From c70d2280c1a5f6c80c834e84c648af8394f7bdb7 Mon Sep 17 00:00:00 2001 From: Ibrahim Halatci Date: Sat, 13 Jun 2026 09:24:36 +0000 Subject: [PATCH] columnar: serialize CREATE INDEX on columnar tables for PG19 PG19 enables parallel CREATE INDEX by default (max_parallel_maintenance_workers defaults to 2). Columnar's TableAM is always serial: rs_parallel is stored but never read, and the build path flushes pending writes, which is disallowed inside a parallel operation. Three changes: 1. Provide working parallelscan_estimate/initialize/reinitialize that delegate to the table_block_* helpers, so callers that unconditionally size and initialize a ParallelTableScanDesc (e.g. PG19's parallel btree build) do not abort. Columnar ignores the descriptor, so the state is simply unused. 2. In columnar_index_build_range_scan, accept a parallel scan descriptor by discarding it (scan = NULL) and running the serial path. 3. In ColumnarProcessUtility, when the statement is CREATE INDEX on a columnar AM relation, force max_parallel_maintenance_workers=0 via NewGUCNestLevel/AtEOXact_GUC around PrevProcessUtilityHook so the build runs serially without triggering "cannot update tuples during a parallel operation". DESCRIPTION: Serialize CREATE INDEX on columnar tables on PG19 Closes #8611 Part of #8597 --- src/backend/columnar/columnar_tableam.c | 58 +++++++++++++++++++++---- 1 file changed, 50 insertions(+), 8 deletions(-) diff --git a/src/backend/columnar/columnar_tableam.c b/src/backend/columnar/columnar_tableam.c index 52087cee4bf..bf4d69cb3c4 100644 --- a/src/backend/columnar/columnar_tableam.c +++ b/src/backend/columnar/columnar_tableam.c @@ -412,24 +412,33 @@ ErrorIfInvalidRowNumber(uint64 rowNumber) } +/* + * Columnar scans are always serial regardless of the parallel scan descriptor + * (rs_parallel is stored but never read by the columnar scan path). We still + * provide working parallelscan callbacks so that callers that unconditionally + * size and initialize a parallel scan descriptor (for example, PG19's parallel + * btree index build path) do not abort. Delegating to the heap block helpers + * is safe: columnar never partitions work using the descriptor, so any state + * written there is simply unused. + */ static Size columnar_parallelscan_estimate(Relation rel) { - elog(ERROR, "columnar_parallelscan_estimate not implemented"); + return sizeof(ParallelBlockTableScanDescData); } static Size columnar_parallelscan_initialize(Relation rel, ParallelTableScanDesc pscan) { - elog(ERROR, "columnar_parallelscan_initialize not implemented"); + return table_block_parallelscan_initialize(rel, pscan); } static void columnar_parallelscan_reinitialize(Relation rel, ParallelTableScanDesc pscan) { - elog(ERROR, "columnar_parallelscan_reinitialize not implemented"); + table_block_parallelscan_reinitialize(rel, pscan); } @@ -1528,10 +1537,13 @@ columnar_index_build_range_scan(Relation columnarRelation, if (scan) { /* - * Parallel scans on columnar tables are already discardad by - * ColumnarGetRelationInfoHook but be on the safe side. + * Starting with PG19 the planner may hand us a parallel scan descriptor + * even for tables (like columnar) whose AM only supports serial scans. + * Discard it: columnar never partitions index-build work across workers + * and always starts its own serial scan below. This mirrors how the + * existing serial path ignores any externally supplied scan. */ - elog(ERROR, "parallel scans on columnar are not supported"); + scan = NULL; } /* @@ -2315,6 +2327,7 @@ ColumnarProcessUtility(PlannedStmt *pstmt, RangeVar *columnarRangeVar = NULL; List *columnarOptions = NIL; + bool indexBuildOnColumnar = false; switch (nodeTag(parsetree)) { @@ -2337,6 +2350,15 @@ ColumnarProcessUtility(PlannedStmt *pstmt, "index on columnar table %s", RelationGetRelationName(rel)))); } + + /* + * Columnar does not support parallel index build (its scan path is + * always serial and writes during the build would violate parallel + * mode). Force max_parallel_maintenance_workers to 0 for this + * statement so PG does not spawn workers. Starting with PG19 the + * default of 2 caused CREATE INDEX on a columnar table to fail. + */ + indexBuildOnColumnar = true; } RelationClose(rel); @@ -2427,8 +2449,28 @@ ColumnarProcessUtility(PlannedStmt *pstmt, CheckCitusColumnarAlterExtensionStmt(parsetree); } - PrevProcessUtilityHook(pstmt, queryString, false, context, - params, queryEnv, dest, completionTag); + int saveNestLevel = -1; + if (indexBuildOnColumnar) + { + saveNestLevel = NewGUCNestLevel(); + (void) set_config_option("max_parallel_maintenance_workers", "0", + PGC_USERSET, PGC_S_SESSION, + GUC_ACTION_SAVE, true, 0, false); + } + + PG_TRY(); + { + PrevProcessUtilityHook(pstmt, queryString, false, context, + params, queryEnv, dest, completionTag); + } + PG_FINALLY(); + { + if (saveNestLevel >= 0) + { + AtEOXact_GUC(true, saveNestLevel); + } + } + PG_END_TRY(); if (columnarOptions != NIL) {