agora inbox for pgsql-hackers@postgresql.org
help / color / mirror / Atom feed[PATCH v44 07/10] Split cluster.h to create repack_internal.h
324+ messages / 1 participants
[nested] [flat]
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)
Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.
Also, change_useless_for_repack() is better declared in decode.h.
---
src/backend/commands/cluster.c | 128 ++++++++++--------
src/backend/postmaster/bgworker.c | 2 +-
.../pgoutput_repack/pgoutput_repack.c | 13 +-
src/include/commands/cluster.h | 57 +-------
src/include/commands/repack_internal.h | 59 ++++++++
src/include/replication/decode.h | 4 +
6 files changed, 147 insertions(+), 116 deletions(-)
create mode 100644 src/include/commands/repack_internal.h
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
char error_queue[FLEXIBLE_ARRAY_MEMBER];
} DecodingWorkerShared;
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
- snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
/*
* Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
*/
volatile sig_atomic_t RepackMessagePending = false;
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
Oid indexOid, Oid userid, LOCKMODE lmode,
int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
static void repack_worker_internal(dsm_segment *seg);
static void export_initial_snapshot(Snapshot snapshot,
DecodingWorkerShared *shared);
static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
- /*
- * Determine the lock mode expected by cluster_rel().
- *
- * In the exclusive case, we obtain AccessExclusiveLock right away to
- * avoid lock-upgrade hazard in the single-transaction case. In the
- * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
- * of processing, supposedly for very short time. Until then, we'll have
- * to unlock the relation temporarily, so there's no lock-upgrade hazard.
- */
- lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
- AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
/*
* If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
Oid ident_idx = InvalidOid;
- /*
- * The lock mode is AccessExclusiveLock for normal processing and
- * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
- * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
- * called concurrently for the same relation).
- */
- lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
- /* There are specific requirements on concurrent processing. */
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
if (concurrent)
{
/*
- * Make sure we have no XID assigned, otherwise call of
- * repack_setup_logical_decoding() can cause a deadlock.
+ * Make sure we're not in a transaction block.
*
- * The existence of transaction block actually does not imply that XID
- * was already assigned, but it very likely is. We might want to check
- * the result of GetCurrentTransactionIdIfAny() instead, but that
- * would be less clear from user's perspective.
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
*/
PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
- *
- * XXX move [some of] these comments to where the RECHECK flag is
- * determined?
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* However it still seems a good practice to make sure that the
* worker never survives the REPACK command.
*/
- stop_decoding_worker();
+ stop_repack_decoding_worker();
}
}
PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
* clustering index) and checking again if it's still eligible for
* REPACK CONCURRENTLY.
*/
- start_decoding_worker(tableOid);
+ start_repack_decoding_worker(tableOid);
/*
* Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
return "???"; /* keep compiler quiet */
}
-
/*
* Is this backend performing logical decoding on behalf of REPACK
* (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
* contents to a new table.
*/
static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
{
Size size;
dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
* we need to stop it explicitly at least on ERROR in the launching backend.
*/
static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
{
BgwHandleStatus status;
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
decoding_worker = NULL;
}
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
/*
* See ParallelWorkerShutdown for details.
*/
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
/*
* Join locking group - see the comments around the call of
- * start_decoding_worker().
+ * start_repack_decoding_worker().
*/
if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
return snapshot;
}
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
bool
IsRepackWorker(void)
{
@@ -4067,7 +4078,6 @@ void
ProcessRepackMessages(void)
{
MemoryContext oldcontext;
-
static MemoryContext hpm_context = NULL;
/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
#include "postgres.h"
#include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
#include "replication/snapbuild.h"
#include "utils/memutils.h"
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
ConcurrentChangeKind kind, HeapTuple tuple)
{
RepackDecodingState *dstate;
- MemoryContext oldcxt;
+ MemoryContext oldcxt;
BufFile *file;
List *attrs_ext = NIL;
int natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
slot_getsomeattrs(slot, i + 1);
- /* This is a non-null varlena datum, but we only care if it's out-of-line */
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
if (!VARATT_IS_EXTERNAL(varlen))
continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
* attributes (those actually should never appear on disk), so
* only TOASTed attribute can be seen here.
*
- * FIXME in what circumstances can an ONDISK attr appear?
- * Why aren't these written separately?
+ * FIXME in what circumstances can an ONDISK attr appear? Why
+ * aren't these written separately?
*/
Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
#ifndef CLUSTER_H
#define CLUSTER_H
-#include "nodes/execnodes.h"
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
#include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
#include "utils/relcache.h"
-#include "utils/resowner.h"
/* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
-
/* options for CLUSTER */
typedef struct ClusterParams
{
bits32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define CHANGE_INSERT 'i'
-#define CHANGE_UPDATE_OLD 'u'
-#define CHANGE_UPDATE_NEW 'U'
-#define CHANGE_DELETE 'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef USE_ASSERT_CHECKING
- /* The relation whose changes we're decoding. */
- Oid relid;
-#endif
-
- /* Per-change memory context. */
- MemoryContext change_cxt;
-
- /* A tuple slot used to pass tuples back and forth */
- TupleTableSlot *slot;
-
- /* The current output file. */
- BufFile *file;
-} RepackDecodingState;
-
extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
-extern void RepackWorkerMain(Datum main_arg);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
--
2.47.3
--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
* [PATCH v52 02/10] Rename cluster.c/h -> repack.c/h
@ 2026-03-31 16:55 Álvaro Herrera <alvherre@kurilemu.de>
0 siblings, 0 replies; 324+ messages in thread
From: Álvaro Herrera @ 2026-03-31 16:55 UTC (permalink / raw)
---
src/backend/commands/Makefile | 2 +-
src/backend/commands/matview.c | 2 +-
src/backend/commands/meson.build | 2 +-
src/backend/commands/{cluster.c => repack.c} | 6 +++---
src/backend/commands/tablecmds.c | 2 +-
src/backend/commands/vacuum.c | 6 +++---
src/backend/storage/ipc/procsignal.c | 1 +
src/backend/tcop/postgres.c | 1 +
src/backend/tcop/utility.c | 2 +-
src/include/commands/{cluster.h => repack.h} | 12 ++++++------
10 files changed, 19 insertions(+), 17 deletions(-)
rename src/backend/commands/{cluster.c => repack.c} (99%)
rename src/include/commands/{cluster.h => repack.h} (90%)
diff --git a/src/backend/commands/Makefile b/src/backend/commands/Makefile
index c10fdba2bbb..fe1bba3a9b9 100644
--- a/src/backend/commands/Makefile
+++ b/src/backend/commands/Makefile
@@ -18,7 +18,6 @@ OBJS = \
amcmds.o \
analyze.o \
async.o \
- cluster.o \
collationcmds.o \
comment.o \
constraint.o \
@@ -51,6 +50,7 @@ OBJS = \
proclang.o \
propgraphcmds.o \
publicationcmds.o \
+ repack.o \
schemacmds.o \
seclabel.o \
sequence.o \
diff --git a/src/backend/commands/matview.c b/src/backend/commands/matview.c
index d3be8939011..5db4fe75dce 100644
--- a/src/backend/commands/matview.c
+++ b/src/backend/commands/matview.c
@@ -24,8 +24,8 @@
#include "catalog/namespace.h"
#include "catalog/pg_am.h"
#include "catalog/pg_opclass.h"
-#include "commands/cluster.h"
#include "commands/matview.h"
+#include "commands/repack.h"
#include "commands/tablecmds.h"
#include "commands/tablespace.h"
#include "executor/executor.h"
diff --git a/src/backend/commands/meson.build b/src/backend/commands/meson.build
index 90c7e37a429..f624aae74af 100644
--- a/src/backend/commands/meson.build
+++ b/src/backend/commands/meson.build
@@ -6,7 +6,6 @@ backend_sources += files(
'amcmds.c',
'analyze.c',
'async.c',
- 'cluster.c',
'collationcmds.c',
'comment.c',
'constraint.c',
@@ -39,6 +38,7 @@ backend_sources += files(
'proclang.c',
'propgraphcmds.c',
'publicationcmds.c',
+ 'repack.c',
'schemacmds.c',
'seclabel.c',
'sequence.c',
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/repack.c
similarity index 99%
rename from src/backend/commands/cluster.c
rename to src/backend/commands/repack.c
index f241e18b153..20f0a572236 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/repack.c
@@ -1,6 +1,6 @@
/*-------------------------------------------------------------------------
*
- * cluster.c
+ * repack.c
* REPACK a table; formerly known as CLUSTER. VACUUM FULL also uses
* parts of this code.
*
@@ -10,7 +10,7 @@
*
*
* IDENTIFICATION
- * src/backend/commands/cluster.c
+ * src/backend/commands/repack.c
*
*-------------------------------------------------------------------------
*/
@@ -33,9 +33,9 @@
#include "catalog/pg_am.h"
#include "catalog/pg_inherits.h"
#include "catalog/toasting.h"
-#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "miscadmin.h"
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 0ce2e81f9c2..e2882a50b3b 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -57,10 +57,10 @@
#include "catalog/storage.h"
#include "catalog/storage_xlog.h"
#include "catalog/toasting.h"
-#include "commands/cluster.h"
#include "commands/comment.h"
#include "commands/defrem.h"
#include "commands/event_trigger.h"
+#include "commands/repack.h"
#include "commands/sequence.h"
#include "commands/tablecmds.h"
#include "commands/tablespace.h"
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 0ed363d1c85..b179b62b5c8 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -9,7 +9,7 @@
*
* VACUUM for heap AM is implemented in vacuumlazy.c, parallel vacuum in
* vacuumparallel.c, ANALYZE in analyze.c, and VACUUM FULL is a variant of
- * CLUSTER, handled in cluster.c.
+ * REPACK, handled in repack.c.
*
*
* Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
@@ -38,9 +38,9 @@
#include "catalog/pg_database.h"
#include "catalog/pg_inherits.h"
#include "commands/async.h"
-#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/progress.h"
+#include "commands/repack.h"
#include "commands/vacuum.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
@@ -2293,7 +2293,7 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
if ((params.options & VACOPT_VERBOSE) != 0)
cluster_params.options |= CLUOPT_VERBOSE;
- /* VACUUM FULL is a variant of REPACK; see cluster.c */
+ /* VACUUM FULL is a variant of REPACK; see repack.c */
cluster_rel(REPACK_COMMAND_VACUUMFULL, rel, InvalidOid,
&cluster_params);
/* cluster_rel closes the relation, but keeps lock */
diff --git a/src/backend/storage/ipc/procsignal.c b/src/backend/storage/ipc/procsignal.c
index 7e017c8d53b..7cef6e43661 100644
--- a/src/backend/storage/ipc/procsignal.c
+++ b/src/backend/storage/ipc/procsignal.c
@@ -19,6 +19,7 @@
#include "access/parallel.h"
#include "commands/async.h"
+#include "commands/repack.h"
#include "miscadmin.h"
#include "pgstat.h"
#include "port/pg_bitutils.h"
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index 10be60011ad..9fbaa5c00f0 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -39,6 +39,7 @@
#include "commands/event_trigger.h"
#include "commands/explain_state.h"
#include "commands/prepare.h"
+#include "commands/repack.h"
#include "common/pg_prng.h"
#include "jit/jit.h"
#include "libpq/libpq.h"
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 2b609bfc824..5f8c766c4be 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -26,7 +26,6 @@
#include "catalog/toasting.h"
#include "commands/alter.h"
#include "commands/async.h"
-#include "commands/cluster.h"
#include "commands/collationcmds.h"
#include "commands/comment.h"
#include "commands/conversioncmds.h"
@@ -46,6 +45,7 @@
#include "commands/proclang.h"
#include "commands/propgraphcmds.h"
#include "commands/publicationcmds.h"
+#include "commands/repack.h"
#include "commands/schemacmds.h"
#include "commands/seclabel.h"
#include "commands/sequence.h"
diff --git a/src/include/commands/cluster.h b/src/include/commands/repack.h
similarity index 90%
rename from src/include/commands/cluster.h
rename to src/include/commands/repack.h
index d6b62c747e8..85061158b0c 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/repack.h
@@ -1,17 +1,17 @@
/*-------------------------------------------------------------------------
*
- * cluster.h
- * header file for postgres cluster command stuff
+ * repack.h
+ * header file for the REPACK command
*
* Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
* Portions Copyright (c) 1994-5, Regents of the University of California
*
- * src/include/commands/cluster.h
+ * src/include/commands/repack.h
*
*-------------------------------------------------------------------------
*/
-#ifndef CLUSTER_H
-#define CLUSTER_H
+#ifndef REPACK_H
+#define REPACK_H
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
@@ -52,4 +52,4 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
MultiXactId cutoffMulti,
char newrelpersistence);
-#endif /* CLUSTER_H */
+#endif /* REPACK_H */
--
2.47.3
--gp2pyozrd5pweboh
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v52-0003-Add-CONCURRENTLY-option-to-REPACK-command.patch"
^ permalink raw reply [nested|flat] 324+ messages in thread
end of thread, other threads:[~2026-03-31 16:55 UTC | newest]
Thread overview: 324+ messages (download: mbox mbox.gz follow: Atom feed)
-- links below jump to the message on this page --
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-31 16:55 [PATCH v52 02/10] Rename cluster.c/h -> repack.c/h Álvaro Herrera <alvherre@kurilemu.de>
This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox