From c4d7478b9e95f669857815a6be33e061aff82e5b Mon Sep 17 00:00:00 2001
From: Jingtang Zhang <mrdrivingduck@gmail.com>
Date: Wed, 9 Sep 2026 20:28:29 +0800
Subject: [PATCH v3] Use maintenance I/O concurrency for index builds

Heap scans for index builds currently use ordinary query I/O
concurrency. Mark serial and parallel index build scans as maintenance
work so their read streams use the maintenance setting, including
tablespace overrides. BRIN range summarization shares the serial scan
path and is covered too.

Preserve the existing scan options and interfaces. The separate heap
validation scan during concurrent index creation is unchanged.
---
 src/backend/access/brin/brin.c           |  2 +-
 src/backend/access/gin/gininsert.c       |  2 +-
 src/backend/access/heap/heapam.c         |  7 +++++--
 src/backend/access/heap/heapam_handler.c | 19 +++++++++++++------
 src/backend/access/nbtree/nbtsort.c      |  2 +-
 src/include/access/tableam.h             |  3 +++
 6 files changed, 24 insertions(+), 11 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index 5059da8dce7..b6f3f87d768 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -2845,7 +2845,7 @@ _brin_parallel_scan_and_build(BrinBuildState *state,
 
 	scan = table_beginscan_parallel(heap,
 									ParallelTableScanFromBrinShared(brinshared),
-									SO_NONE);
+									SO_MAINTENANCE);
 
 	reltuples = table_index_build_scan(heap, index, indexInfo, true, true,
 									   brinbuildCallbackParallel, state, scan);
diff --git a/src/backend/access/gin/gininsert.c b/src/backend/access/gin/gininsert.c
index 37f689a2cac..07b5ca82167 100644
--- a/src/backend/access/gin/gininsert.c
+++ b/src/backend/access/gin/gininsert.c
@@ -2066,7 +2066,7 @@ _gin_parallel_scan_and_build(GinBuildState *state,
 
 	scan = table_beginscan_parallel(heap,
 									ParallelTableScanFromGinBuildShared(ginshared),
-									SO_NONE);
+									SO_MAINTENANCE);
 
 	reltuples = table_index_build_scan(heap, index, indexInfo, true, progress,
 									   ginBuildCallbackParallel, state, scan);
diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index 72d6541734c..1bb5d6afa64 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -1281,6 +1281,10 @@ heap_beginscan(Relation relation, Snapshot snapshot,
 		scan->rs_base.rs_flags & SO_TYPE_TIDRANGESCAN)
 	{
 		ReadStreamBlockNumberCB cb;
+		int			stream_flags = READ_STREAM_SEQUENTIAL | READ_STREAM_USE_BATCHING;
+
+		if (scan->rs_base.rs_flags & SO_MAINTENANCE)
+			stream_flags |= READ_STREAM_MAINTENANCE;
 
 		if (scan->rs_base.rs_parallel)
 			cb = heap_scan_stream_read_next_parallel;
@@ -1294,8 +1298,7 @@ heap_beginscan(Relation relation, Snapshot snapshot,
 		 * - in the parallel case, only spinlocks and atomics are used
 		 * ---
 		 */
-		scan->rs_read_stream = read_stream_begin_relation(READ_STREAM_SEQUENTIAL |
-														  READ_STREAM_USE_BATCHING,
+		scan->rs_read_stream = read_stream_begin_relation(stream_flags,
 														  scan->rs_strategy,
 														  scan->rs_base.rs_rd,
 														  MAIN_FORKNUM,
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 0f24a132564..9c3536eaa1b 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -1211,6 +1211,9 @@ heapam_index_build_range_scan(Relation heapRelation,
 
 	if (!scan)
 	{
+		uint32		internal_flags = SO_TYPE_SEQSCAN |
+			SO_ALLOW_PAGEMODE | SO_ALLOW_STRAT;
+
 		/*
 		 * Serial index build.
 		 *
@@ -1225,12 +1228,16 @@ heapam_index_build_range_scan(Relation heapRelation,
 		else
 			snapshot = SnapshotAny;
 
-		scan = table_beginscan_strat(heapRelation,	/* relation */
-									 snapshot,	/* snapshot */
-									 0, /* number of keys */
-									 NULL,	/* scan key */
-									 true,	/* buffer access strategy OK */
-									 allow_sync);	/* syncscan OK? */
+		if (allow_sync)
+			internal_flags |= SO_ALLOW_SYNC;
+
+		scan = table_beginscan_common(heapRelation, /* relation */
+									  snapshot, /* snapshot */
+									  0,	/* number of keys */
+									  NULL, /* scan key */
+									  NULL, /* serial scan */
+									  internal_flags,
+									  SO_MAINTENANCE);
 	}
 	else
 	{
diff --git a/src/backend/access/nbtree/nbtsort.c b/src/backend/access/nbtree/nbtsort.c
index 756dfa3dcf4..ff5ed01718c 100644
--- a/src/backend/access/nbtree/nbtsort.c
+++ b/src/backend/access/nbtree/nbtsort.c
@@ -1929,7 +1929,7 @@ _bt_parallel_scan_and_sort(BTSpool *btspool, BTSpool *btspool2,
 	indexInfo->ii_Concurrent = btshared->isconcurrent;
 	scan = table_beginscan_parallel(btspool->heap,
 									ParallelTableScanFromBTShared(btshared),
-									SO_NONE);
+									SO_MAINTENANCE);
 	reltuples = table_index_build_scan(btspool->heap, btspool->index, indexInfo,
 									   true, progress, _bt_build_callback,
 									   &buildstate, scan);
diff --git a/src/include/access/tableam.h b/src/include/access/tableam.h
index ff03a2b816f..31f49814433 100644
--- a/src/include/access/tableam.h
+++ b/src/include/access/tableam.h
@@ -72,6 +72,9 @@ typedef enum ScanOptions
 
 	/* collect scan instrumentation */
 	SO_SCAN_INSTRUMENT = 1 << 11,
+
+	/* use maintenance I/O concurrency */
+	SO_MAINTENANCE = 1 << 12,
 }			ScanOptions;
 
 /*
-- 
2.43.7

