You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cloudberry.apache.org by ro...@apache.org on 2026/10/23 02:39:57 UTC

(cloudberry) 01/02: init

This is an automated email from the ASF dual-hosted git repository.

roseduan pushed a commit to branch refactor_ao_parallel_scan
in repository https://gitbox.apache.org/repos/asf/cloudberry.git

commit aba36947a3ce72141f16af12bfa8a877b2e6d7ca
Author: roseduan <ro...@163.com>
AuthorDate: Tue Oct 20 01:33:22 2026 -0700

    init
---
 src/backend/access/aocs/aocsam.c         | 362 ++++++++++++++++++++++++++++++-
 src/backend/access/aocs/aocsam_handler.c |  41 ++--
 src/backend/access/table/tableam.c       |  13 +-
 src/include/access/relscan.h             |  74 +++++++
 src/include/cdb/cdbaocsam.h              |  22 ++
 5 files changed, 492 insertions(+), 20 deletions(-)

diff --git a/src/backend/access/aocs/aocsam.c b/src/backend/access/aocs/aocsam.c
index 244d1da808d..e0dec910d7d 100644
--- a/src/backend/access/aocs/aocsam.c
+++ b/src/backend/access/aocs/aocsam.c
@@ -352,24 +352,338 @@ initscan_with_colinfo(AOCSScanDesc scan)
 	pgstat_count_heap_scan(scan->rs_base.rs_rd);
 }
 
+/* ----------------------------------------------------------------------------
+ * POC: leader-side chunk map construction.
+ *
+ * Divides each segment file into row ranges whose boundaries come from real
+ * block directory entries of an "anchor" column.  We deliberately do NOT split
+ * the row number space arithmetically: gp_fastsequence allocates row numbers in
+ * batches of NUM_FAST_SEQUENCES and burns whatever a transaction does not use,
+ * so row number is not a proxy for row ordinal.  Measured density is ~99.98%
+ * for a bulk load but drops to ~10% after aborted transactions, which would
+ * give wildly uneven chunks.  Taking boundaries straight from the directory is
+ * immune to that by construction.
+ *
+ * Only the anchor column's *directory* is consulted here; its data file is not
+ * opened.  Other columns are positioned at scan time by probing the directory
+ * with the same row number (see position_all_columns()), which is legal because
+ * AppendOnlyBlockDirectory_GetEntry() resolves an arbitrary row number to the
+ * entry whose range covers it.  Columns therefore never need aligned block
+ * boundaries -- a bigint column packs ~4090 rows/block while an int column
+ * packs ~8181, and both resolve the same row correctly.
+ *
+ * Leaves aopscan->nchunks == 0 if the relation has no block directory, in which
+ * case the scan falls back to the existing segfile-granular behaviour.
+ * ----------------------------------------------------------------------------
+ */
+void
+aocs_parallelscan_buildchunks(Relation rel,
+							  Snapshot metaSnapshot,
+							  ParallelAOScanDesc aopscan,
+							  struct AOCSFileSegInfo **seginfo,
+							  int nseg)
+{
+	AppendOnlyBlockDirectory blkdir;
+	AppendOnlyBlockDirectoryEntry entry;
+	Oid			blkdirrelid = InvalidOid;
+	bool	   *proj;
+	int			natts = RelationGetNumberOfAttributes(rel);
+	int			anchor = 0;
+	int64		total_blocks = 0;
+	int			blocks_per_chunk;
+	int			nchunks = 0;
+	int			i;
+
+	aopscan->nchunks = 0;
+	aopscan->anchor_col = anchor;
+
+	if (nseg <= 0)
+		return;
+
+	GetAppendOnlyEntryAuxOids(rel, NULL, &blkdirrelid, NULL, NULL, NULL);
+	if (!OidIsValid(blkdirrelid))
+		return;					/* no block directory -> caller falls back */
+
+	/* We only need the anchor column group's minipages. */
+	proj = (bool *) palloc0(natts * sizeof(bool));
+	proj[anchor] = true;
+
+	AppendOnlyBlockDirectory_Init_forSearch(&blkdir,
+											metaSnapshot,
+											(FileSegInfo **) seginfo,
+											nseg,
+											rel,
+											natts,
+											true /* isAOCol */,
+											proj);
+
+	/*
+	 * Pass 1: count blocks so we can size chunks to fit the fixed DSM budget.
+	 * Two passes keep leader memory bounded; materialising every boundary
+	 * first would cost 16 bytes per block.
+	 */
+	for (i = 0; i < nseg; i++)
+	{
+		int64		row = 1;
+
+		for (;;)
+		{
+			AOTupleId	tid;
+
+			AOTupleIdInit(&tid, seginfo[i]->segno, row);
+			if (!AppendOnlyBlockDirectory_GetEntry(&blkdir, &tid, anchor, &entry))
+				break;
+
+
+			/*
+			 * GetEntry() clamps to the last entry rather than failing when the
+			 * probe row is past the end of the segment file, so "the range did
+			 * not advance" is our end-of-data condition.  It also guarantees
+			 * forward progress, which this loop depends on.
+			 */
+			if (entry.range.lastRowNum < row)
+				break;
+
+			total_blocks++;
+			row = entry.range.lastRowNum + 1;
+		}
+	}
+
+	if (total_blocks == 0)
+	{
+		AppendOnlyBlockDirectory_End_forSearch(&blkdir);
+		pfree(proj);
+		return;
+	}
+
+	blocks_per_chunk = (int) ((total_blocks + AO_PARALLEL_MAX_CHUNKS - 1) /
+							  AO_PARALLEL_MAX_CHUNKS);
+	if (blocks_per_chunk < 1)
+		blocks_per_chunk = 1;
+
+	/* Pass 2: emit a chunk every blocks_per_chunk directory entries. */
+	for (i = 0; i < nseg && nchunks < AO_PARALLEL_MAX_CHUNKS; i++)
+	{
+		int64		row = 1;
+		int64		chunk_start = -1;
+		int64		chunk_last = -1;
+		int			nblocks_in_chunk = 0;
+
+		while (nchunks < AO_PARALLEL_MAX_CHUNKS)
+		{
+			AOTupleId	tid;
+
+			AOTupleIdInit(&tid, seginfo[i]->segno, row);
+			if (!AppendOnlyBlockDirectory_GetEntry(&blkdir, &tid, anchor, &entry))
+				break;
+
+			if (entry.range.lastRowNum < row)
+				break;			/* past end of this segment file */
+
+			if (chunk_start < 0)
+				chunk_start = entry.range.firstRowNum;
+			chunk_last = entry.range.lastRowNum;
+			nblocks_in_chunk++;
+			row = entry.range.lastRowNum + 1;
+
+			if (nblocks_in_chunk >= blocks_per_chunk)
+			{
+				aopscan->chunks[nchunks].segno = seginfo[i]->segno;
+				aopscan->chunks[nchunks].start_row = chunk_start;
+				aopscan->chunks[nchunks].end_row = chunk_last;
+				nchunks++;
+				chunk_start = -1;
+				nblocks_in_chunk = 0;
+			}
+		}
+
+		/* flush a partial trailing chunk for this segfile */
+		if (chunk_start >= 0 && nchunks < AO_PARALLEL_MAX_CHUNKS)
+		{
+			aopscan->chunks[nchunks].segno = seginfo[i]->segno;
+			aopscan->chunks[nchunks].start_row = chunk_start;
+			aopscan->chunks[nchunks].end_row = chunk_last;
+			nchunks++;
+		}
+	}
+
+	AppendOnlyBlockDirectory_End_forSearch(&blkdir);
+	pfree(proj);
+
+	aopscan->nchunks = nchunks;
+
+	elog(DEBUG1,
+		 "aocs parallel scan: rel \"%s\" %d segfiles, " INT64_FORMAT
+		 " anchor blocks, %d blocks/chunk, %d chunks",
+		 RelationGetRelationName(rel), nseg, total_blocks,
+		 blocks_per_chunk, nchunks);
+}
+
+/* forward declaration: defined below, used by the POC chunk path */
+static void close_cur_scan_seg(AOCSScanDesc scan);
+
+/*
+ * POC: lazily set up this worker's private block directory handle, used only
+ * to translate a row number into a per-column file offset.
+ */
+static void
+init_positioning_blkdir(AOCSScanDesc scan)
+{
+	Relation	rel = scan->rs_base.rs_rd;
+	int			natts = RelationGetNumberOfAttributes(rel);
+	bool	   *proj;
+
+	if (scan->posBlkdir != NULL)
+		return;
+
+	proj = (bool *) palloc0(natts * sizeof(bool));
+	for (AttrNumber i = 0; i < scan->columnScanInfo.num_proj_atts; i++)
+		proj[scan->columnScanInfo.proj_atts[i]] = true;
+
+	scan->posBlkdir = (AppendOnlyBlockDirectory *)
+		palloc0(sizeof(AppendOnlyBlockDirectory));
+
+	AppendOnlyBlockDirectory_Init_forSearch(scan->posBlkdir,
+											scan->appendOnlyMetaDataSnapshot,
+											(FileSegInfo **) scan->seginfo,
+											scan->total_seg,
+											rel,
+											natts,
+											true /* isAOCol */,
+											proj);
+}
+
+/*
+ * POC: position every projected column so that the caller's next
+ * datumstreamread_advance() yields row `targetRow`.
+ *
+ * Each column resolves the SAME row number to its OWN byte offset.  Columns do
+ * not share block boundaries -- e.g. in a table with a bigint and an int
+ * column, row 102251 is the first row of the bigint column's block 25 (offset
+ * 819000, no skip) but sits 4078 rows inside the int column's block 12 (offset
+ * 393216).  That asymmetry is expected and is exactly what
+ * AppendOnlyBlockDirectory_GetEntry() + datumstreamread_find() handle.
+ */
+static bool
+position_all_columns(AOCSScanDesc scan, int32 segno, int64 targetRow)
+{
+	AppendOnlyBlockDirectoryEntry entry;
+	AOTupleId	tid;
+
+	AOTupleIdInit(&tid, segno, targetRow);
+
+	for (AttrNumber i = 0; i < scan->columnScanInfo.num_proj_atts; i++)
+	{
+		AttrNumber	attno = scan->columnScanInfo.proj_atts[i];
+		DatumStreamRead *ds = scan->columnScanInfo.ds[attno];
+
+		if (!AppendOnlyBlockDirectory_GetEntry(scan->posBlkdir, &tid, attno, &entry))
+			return false;
+
+		/* Seek the storage reader to the block covering targetRow. */
+		AppendOnlyStorageRead_SetTemporaryRange(&ds->ao_read,
+												entry.range.fileOffset,
+												ds->eof);
+
+		if (datumstreamread_block(ds, NULL, attno) < 0)
+			return false;
+
+		Assert(ds->blockFirstRowNum <= targetRow);
+
+		/*
+		 * Skip forward inside the block, stopping one short because the
+		 * caller's loop starts with datumstreamread_advance().  find() walks
+		 * row by row, but advance() is an inlined pointer bump, so the cost is
+		 * ~half a block per column per chunk boundary -- under 1% at the chunk
+		 * sizes we generate.
+		 */
+		datumstreamread_find(ds, targetRow - ds->blockFirstRowNum - 1);
+	}
+
+	return true;
+}
+
+/*
+ * POC: claim the next row-range chunk.  This replaces open_next_scan_seg() on
+ * the parallel path.  The atomic fetch-add is the entire load balancing
+ * strategy: because chunks are numerous and uniform, a worker that finishes
+ * early simply takes the next one, so no work stealing is needed.
+ *
+ * Returns the seginfo index positioned for scanning, or -1 when the scan is
+ * exhausted.
+ */
+static int
+claim_next_chunk(AOCSScanDesc scan)
+{
+	ParallelAOScanDesc aopscan = (ParallelAOScanDesc) scan->rs_base.rs_parallel;
+
+
+	init_positioning_blkdir(scan);
+
+	for (;;)
+	{
+		uint64		i;
+		int			s;
+
+		i = pg_atomic_fetch_add_u64(&aopscan->cur_chunk, 1);
+		if (i >= (uint64) aopscan->nchunks)
+			return -1;
+
+		scan->cur_chunk = aopscan->chunks[i];
+
+		/* Find the seginfo slot for this chunk's segment file. */
+		for (s = 0; s < scan->total_seg; s++)
+			if (scan->seginfo[s]->segno == scan->cur_chunk.segno)
+				break;
+		if (s >= scan->total_seg)
+		{
+			continue;			/* segfile not visible to us; skip the chunk */
+		}
+
+		if (scan->cur_seg != s)
+		{
+			close_cur_scan_seg(scan);
+			scan->cur_seg = s;
+			open_all_datumstreamread_segfiles(scan, scan->seginfo[s]);
+		}
+
+		if (!position_all_columns(scan, scan->cur_chunk.segno,
+								  scan->cur_chunk.start_row))
+		{
+			continue;
+		}
+
+		scan->segrowsprocessed = 0;
+		return scan->cur_seg;
+	}
+}
+
 static int
 open_next_scan_seg(AOCSScanDesc scan)
 {
 	bool 		isParallel = false;
-	ParallelBlockTableScanDesc pbscan = NULL;
+	ParallelAOScanDesc pbscan = NULL;
+
+	/*
+	 * POC: when a chunk map is available the unit of work is a row range, not
+	 * a whole segment file.  Everything below is the legacy segfile-granular
+	 * path, kept as the fallback for relations with no block directory.
+	 */
+	if (scan->chunked)
+		return claim_next_chunk(scan);
 
 	if (scan->rs_base.rs_parallel != NULL)
 	{
 		isParallel = true;
-		pbscan = (ParallelBlockTableScanDesc) scan->rs_base.rs_parallel;
+		pbscan = (ParallelAOScanDesc) scan->rs_base.rs_parallel;
 	}
 
 	while (scan->cur_seg < scan->total_seg)
 	{
 		if (isParallel)
 		{
-			scan->cur_seg = pg_atomic_fetch_add_u64(&pbscan->phs_nallocated, 1);
-			if (scan->cur_seg >= pbscan->phs_nblocks)
+			scan->cur_seg = pg_atomic_fetch_add_u64(&pbscan->cur_segfile, 1);
+			if (scan->cur_seg >= pbscan->nsegfiles)
 				break;
 		}
 		else
@@ -600,6 +914,15 @@ aocs_beginscan_internal(Relation relation,
 	scan->appendOnlyMetaDataSnapshot = appendOnlyMetaDataSnapshot;
 	scan->seginfo = seginfo;
 	scan->total_seg = total_seg;
+
+	/*
+	 * POC: use block-granular chunks only if the leader managed to build a
+	 * chunk map (which requires a block directory).  Otherwise fall through to
+	 * the existing segfile-granular behaviour.
+	 */
+	scan->chunked = (parallel_scan != NULL &&
+					 ((ParallelAOScanDesc) parallel_scan)->nchunks > 0);
+	scan->posBlkdir = NULL;
 	scan->columnScanInfo.scanCtx = CurrentMemoryContext;
 
 	/* relationTupleDesc will be inited by the slot when needed */
@@ -683,6 +1006,14 @@ aocs_endscan(AOCSScanDesc scan)
 {
 	close_cur_scan_seg(scan);
 
+	/* POC: release this worker's positioning block directory handle */
+	if (scan->posBlkdir != NULL)
+	{
+		AppendOnlyBlockDirectory_End_forSearch(scan->posBlkdir);
+		pfree(scan->posBlkdir);
+		scan->posBlkdir = NULL;
+	}
+
 	if (scan->columnScanInfo.ds)
 	{
 		Assert(scan->columnScanInfo.proj_atts);
@@ -1220,6 +1551,29 @@ ReadNext:
 					rowNum = scan->columnScanInfo.ds[attno]->blockFirstRowNum +
 						datumstreamread_nth(scan->columnScanInfo.ds[attno]);
 				}
+
+				/*
+				 * POC: enforce this worker's chunk boundary.  Setting err = -1
+				 * and jumping back routes through the "open next" path, which
+				 * dispatches to claim_next_chunk().  Without this the worker
+				 * would run to the end of the segment file, i.e. the old
+				 * behaviour.
+				 */
+				if (scan->chunked &&
+					rowNum != INT64CONST(-1) &&
+					rowNum > scan->cur_chunk.end_row)
+				{
+					/*
+					 * rowNum is only recomputed when it is -1, so it MUST be
+					 * reset here.  Otherwise the next chunk compares this stale
+					 * row number against its own end_row, fires the budget
+					 * immediately and yields zero rows.
+					 */
+					rowNum = INT64CONST(-1);
+					err = -1;
+					goto ReadNext;
+				}
+
 				scan->segrowsprocessed++;
 				if (rowNum == INT64CONST(-1))
 				{
diff --git a/src/backend/access/aocs/aocsam_handler.c b/src/backend/access/aocs/aocsam_handler.c
index 4dc7d541c5f..811f01f7bc8 100644
--- a/src/backend/access/aocs/aocsam_handler.c
+++ b/src/backend/access/aocs/aocsam_handler.c
@@ -780,35 +780,48 @@ aoco_scan_flags(Relation rel)
 	return SCAN_SUPPORT_COLUMN_ORIENTED_SCAN | SCAN_SUPPORT_VECTORIZATION;
 }
 
+/*
+ * POC: AOCS now uses its own ParallelAOScanDescData rather than borrowing
+ * heap's ParallelBlockTableScanDescData and reinterpreting its fields.  The
+ * DSM size must be fixed here because parallelscan_estimate() has no snapshot
+ * and therefore cannot know how many chunks the table will yield.
+ */
 static Size
 aoco_parallelscan_estimate(Relation rel)
 {
-	return sizeof(ParallelBlockTableScanDescData);
+	return SizeOfParallelAOScanDesc;
 }
 
-/*
- * AOCO only uses part fields of ParallelBlockTableScanDesc.
- */
 static Size
 aoco_parallelscan_initialize(Relation rel, ParallelTableScanDesc pscan)
 {
-	ParallelBlockTableScanDesc bpscan = (ParallelBlockTableScanDesc) pscan;
+	ParallelAOScanDesc aopscan = (ParallelAOScanDesc) pscan;
+
+	aopscan->base.phs_relid = RelationGetRelid(rel);
+
+	/*
+	 * nchunks and nsegfiles need a snapshot to compute (they come from the
+	 * aocsseg catalog and the block directory), so they are filled in later by
+	 * table_parallelscan_initialize().  nchunks == 0 means "no block directory
+	 * available" and the scan falls back to segfile granularity.
+	 */
+	aopscan->nchunks = 0;
+	aopscan->anchor_col = 0;
+	aopscan->nsegfiles = 0;
+
+	pg_atomic_init_u64(&aopscan->cur_chunk, 0);
+	pg_atomic_init_u64(&aopscan->cur_segfile, 0);
 
-	bpscan->base.phs_relid = RelationGetRelid(rel);
-	bpscan->phs_nblocks = 0; /* init, will be updated later by table_parallelscan_initialize */
-	pg_atomic_init_u64(&bpscan->phs_nallocated, 0);
-	/* we don't need phs_mutex and phs_startblock in ao, though, init them. */
-	SpinLockInit(&bpscan->phs_mutex);
-	bpscan->phs_startblock = InvalidBlockNumber;
-	return sizeof(ParallelBlockTableScanDescData);
+	return SizeOfParallelAOScanDesc;
 }
 
 static void
 aoco_parallelscan_reinitialize(Relation rel, ParallelTableScanDesc pscan)
 {
-	ParallelBlockTableScanDesc bpscan = (ParallelBlockTableScanDesc) pscan;
+	ParallelAOScanDesc aopscan = (ParallelAOScanDesc) pscan;
 
-	pg_atomic_write_u64(&bpscan->phs_nallocated, 0);
+	pg_atomic_write_u64(&aopscan->cur_chunk, 0);
+	pg_atomic_write_u64(&aopscan->cur_segfile, 0);
 }
 
 static IndexFetchTableData *
diff --git a/src/backend/access/table/tableam.c b/src/backend/access/table/tableam.c
index 70cb8cc9cc7..5c20147899a 100644
--- a/src/backend/access/table/tableam.c
+++ b/src/backend/access/table/tableam.c
@@ -32,6 +32,7 @@
 
 #include "access/aosegfiles.h"
 #include "access/aocssegfiles.h"
+#include "cdb/cdbaocsam.h"		/* POC: aocs_parallelscan_buildchunks */
 
 /*
  * Constants to control the behavior of block allocation to parallel workers
@@ -206,7 +207,7 @@ table_parallelscan_initialize(Relation rel, ParallelTableScanDesc pscan,
 	else if (RelationIsAoCols(rel))
 	{
 		Snapshot appendOnlyMetaDataSnapshot = snapshot;
-		ParallelBlockTableScanDesc bpscan = (ParallelBlockTableScanDesc) pscan;
+		ParallelAOScanDesc aopscan = (ParallelAOScanDesc) pscan;
 		int			segfile_count;
 		AOCSFileSegInfo **seginfo;
 		if (snapshot == SnapshotAny)
@@ -218,7 +219,15 @@ table_parallelscan_initialize(Relation rel, ParallelTableScanDesc pscan,
 			appendOnlyMetaDataSnapshot = GetTransactionSnapshot();
 		}
 		seginfo = GetAllAOCSFileSegInfo(rel, appendOnlyMetaDataSnapshot, &segfile_count, NULL);
-		bpscan->phs_nblocks = segfile_count;
+		aopscan->nsegfiles = segfile_count;
+
+		/*
+		 * POC: try to build a block-granular chunk map.  If the relation has
+		 * no block directory this leaves nchunks == 0 and the scan falls back
+		 * to claiming whole segment files, i.e. today's behaviour.
+		 */
+		aocs_parallelscan_buildchunks(rel, appendOnlyMetaDataSnapshot,
+									  aopscan, seginfo, segfile_count);
 		if (seginfo)
 		{
 			FreeAllAOCSSegFileInfo(seginfo, segfile_count);
diff --git a/src/include/access/relscan.h b/src/include/access/relscan.h
index 2f27e9fc6a2..1c1c29aaebe 100644
--- a/src/include/access/relscan.h
+++ b/src/include/access/relscan.h
@@ -95,6 +95,80 @@ typedef struct ParallelBlockTableScanDescData *ParallelBlockTableScanDesc;
 /*
  * Per backend state for parallel table scan, for block-oriented storage.
  */
+/* ----------------------------------------------------------------------------
+ * POC: block-granular parallel scan for AO/AOCS.
+ *
+ * Today AO/AOCS reuse ParallelBlockTableScanDescData and reinterpret its
+ * fields so that the unit of parallel work is an entire segment file.  That
+ * caps parallelism at the segfile count (and at *zero* when there is only one
+ * segfile) and leaves segfile size skew completely unmitigated.
+ *
+ * This descriptor instead hands out *row ranges*.  A row range is the only
+ * workable unit for AOCS: blocks are variable length, so there is no
+ * offset = f(block number), and the N column files of one segfile must stay
+ * row-aligned with each other.  Boundaries are derived from the block
+ * directory rather than computed arithmetically, because row numbers are
+ * ordered but NOT contiguous -- gp_fastsequence hands them out in batches of
+ * NUM_FAST_SEQUENCES and burns whatever a transaction does not use.
+ * ----------------------------------------------------------------------------
+ */
+
+/*
+ * One unit of parallel work: rows [start_row, end_row] of segment file segno.
+ * Both bounds are inclusive and are real row numbers taken from block
+ * directory entries, so gaps in the row number space cannot produce an
+ * empty or oversized chunk.
+ */
+typedef struct AOParallelScanChunk
+{
+	int32		segno;
+	int64		start_row;
+	int64		end_row;
+} AOParallelScanChunk;
+
+/*
+ * parallelscan_estimate() is called without a snapshot, so we cannot know the
+ * real chunk count when the DSM segment is sized.  Reserve a fixed ceiling
+ * instead; at 24 bytes per chunk this costs ~48kB, which is noise next to the
+ * tuple queues.  Chunks are sized to fit this budget (see
+ * aoco_parallelscan_buildchunks).
+ */
+#define AO_PARALLEL_MAX_CHUNKS		2048
+
+typedef struct ParallelAOScanDescData
+{
+	ParallelTableScanDescData base;
+
+	/*
+	 * Number of chunks actually populated by the leader.  Zero means "no block
+	 * directory available" and the scan silently falls back to today's
+	 * segfile-granular behaviour, which is what makes this safe to ship
+	 * incrementally.
+	 */
+	int32		nchunks;
+
+	/*
+	 * Column group whose block directory entries defined the chunk boundaries.
+	 * Only its *directory* is read; its data file is not touched unless the
+	 * query happens to project it.
+	 */
+	int32		anchor_col;
+
+	/* Chunk cursor.  This is the entire load balancing strategy. */
+	pg_atomic_uint64 cur_chunk;
+
+	/* --- fallback path: segfile-granular, mirrors the current behaviour --- */
+	int32		nsegfiles;
+	pg_atomic_uint64 cur_segfile;
+
+	AOParallelScanChunk chunks[FLEXIBLE_ARRAY_MEMBER];
+} ParallelAOScanDescData;
+typedef struct ParallelAOScanDescData *ParallelAOScanDesc;
+
+#define SizeOfParallelAOScanDesc \
+	(offsetof(ParallelAOScanDescData, chunks) + \
+	 AO_PARALLEL_MAX_CHUNKS * sizeof(AOParallelScanChunk))
+
 typedef struct ParallelBlockTableScanWorkerData
 {
 	uint64		phsw_nallocated;	/* Current # of blocks into the scan */
diff --git a/src/include/cdb/cdbaocsam.h b/src/include/cdb/cdbaocsam.h
index 68c62b55b99..2c3f1c702b4 100644
--- a/src/include/cdb/cdbaocsam.h
+++ b/src/include/cdb/cdbaocsam.h
@@ -252,6 +252,19 @@ typedef struct AOCSScanDescData
 	int32					 total_seg;
 	int32					 cur_seg;
 
+	/*
+	 * POC: block-granular parallel scan state.
+	 *
+	 * When "chunked" is set this worker claims row ranges out of the shared
+	 * ParallelAOScanDesc instead of whole segment files.  cur_chunk holds the
+	 * range currently being scanned; posBlkdir is this worker's private block
+	 * directory handle, used only to translate a row number into a per-column
+	 * file offset at chunk boundaries.
+	 */
+	bool					 chunked;
+	AOParallelScanChunk		 cur_chunk;
+	struct AppendOnlyBlockDirectory *posBlkdir;
+
 	/*
 	 * The only relation wide Storage Option, the rest are aquired in a per
 	 * column basis and there is no need to keep track of.
@@ -366,6 +379,15 @@ typedef AOCSAddColumnDescData *AOCSAddColumnDesc;
  * ----------------
  */
 
+struct ParallelAOScanDescData;
+
+/* POC: build the parallel scan chunk map from the block directory */
+extern void aocs_parallelscan_buildchunks(Relation rel,
+										  Snapshot metaSnapshot,
+										  struct ParallelAOScanDescData *aopscan,
+										  struct AOCSFileSegInfo **seginfo,
+										  int nseg);
+
 extern AOCSScanDesc aocs_beginscan(Relation relation, Snapshot snapshot, ParallelTableScanDesc parallel_scan,
 								   bool *proj, uint32 flags);
 extern AOCSScanDesc aocs_beginrangescan(Relation relation, Snapshot snapshot,


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@cloudberry.apache.org
For additional commands, e-mail: commits-help@cloudberry.apache.org