From fec8ab8c5bac3ed43cec1ccbbbb53e0fafb541eb Mon Sep 17 00:00:00 2001
From: Tomas Vondra <tomas@vondra.me>
Date: Wed, 18 Mar 2026 16:03:47 +0100
Subject: [PATCH v3 4/6] store the IO stats in scan descriptor

- Don't pass the stats through the TAM interface. Instead, make them
  part of the scan descriptor (optionally).

- ambeginscan() may allocate IOStats in the scan descriptor, and fill it
  with data. Being part of the decriptor means it won't survive
  amendscan().

- The IOStats is then passed to the ReadStream through a new argument
  added to read_stream_begin_relation(), and the stream updates this
  place directly. There's no need to "fetch" the stats from the stream.

- With parallel query, the data from workers is still copied through
  shared memory by endscan() in the workers. The leader has to consume
  and merge the data. The only difference is that the workers read the
  data from the scan descriptor, and not from the instrumentation.
---
 contrib/amcheck/verify_heapam.c           |  3 +-
 contrib/bloom/blscan.c                    |  3 +-
 contrib/bloom/blvacuum.c                  |  6 ++-
 contrib/pg_prewarm/autoprewarm.c          |  3 +-
 contrib/pg_prewarm/pg_prewarm.c           |  3 +-
 contrib/pg_visibility/pg_visibility.c     |  6 ++-
 contrib/pgstattuple/pgstatapprox.c        |  3 +-
 contrib/pgstattuple/pgstatindex.c         |  6 ++-
 src/backend/access/brin/brin.c            |  3 +-
 src/backend/access/gin/ginvacuum.c        |  3 +-
 src/backend/access/gist/gistvacuum.c      |  3 +-
 src/backend/access/hash/hash.c            |  3 +-
 src/backend/access/heap/heapam.c          | 41 +++-------------
 src/backend/access/heap/heapam_handler.c  |  1 -
 src/backend/access/heap/vacuumlazy.c      |  6 ++-
 src/backend/access/nbtree/nbtree.c        |  3 +-
 src/backend/access/spgist/spgvacuum.c     |  3 +-
 src/backend/commands/analyze.c            |  3 +-
 src/backend/commands/explain.c            | 31 ++----------
 src/backend/executor/nodeBitmapHeapscan.c |  6 +--
 src/backend/executor/nodeSeqscan.c        |  6 +--
 src/backend/storage/aio/read_stream.c     | 59 ++++++++++++++---------
 src/include/access/heapam.h               |  1 -
 src/include/access/relscan.h              |  6 +++
 src/include/access/tableam.h              | 17 -------
 src/include/nodes/execnodes.h             |  1 -
 src/include/storage/read_stream.h         |  5 +-
 27 files changed, 97 insertions(+), 137 deletions(-)

diff --git a/contrib/amcheck/verify_heapam.c b/contrib/amcheck/verify_heapam.c
index ada782f98f5..0ec4dc849c3 100644
--- a/contrib/amcheck/verify_heapam.c
+++ b/contrib/amcheck/verify_heapam.c
@@ -476,7 +476,8 @@ verify_heapam(PG_FUNCTION_ARGS)
 										MAIN_FORKNUM,
 										stream_cb,
 										stream_data,
-										0);
+										0,
+										NULL);
 
 	while ((ctx.buffer = read_stream_next_buffer(stream, NULL)) != InvalidBuffer)
 	{
diff --git a/contrib/bloom/blscan.c b/contrib/bloom/blscan.c
index 1a0e42021ec..9bdc4a95c18 100644
--- a/contrib/bloom/blscan.c
+++ b/contrib/bloom/blscan.c
@@ -138,7 +138,8 @@ blgetbitmap(IndexScanDesc scan, TIDBitmap *tbm)
 										MAIN_FORKNUM,
 										block_range_read_stream_cb,
 										&p,
-										0);
+										0,
+										NULL);
 
 	for (blkno = BLOOM_HEAD_BLKNO; blkno < npages; blkno++)
 	{
diff --git a/contrib/bloom/blvacuum.c b/contrib/bloom/blvacuum.c
index 6beb1c20ebb..587757c1e18 100644
--- a/contrib/bloom/blvacuum.c
+++ b/contrib/bloom/blvacuum.c
@@ -71,7 +71,8 @@ blbulkdelete(IndexVacuumInfo *info, IndexBulkDeleteResult *stats,
 										MAIN_FORKNUM,
 										block_range_read_stream_cb,
 										&p,
-										0);
+										0,
+										NULL);
 
 	for (blkno = BLOOM_HEAD_BLKNO; blkno < npages; blkno++)
 	{
@@ -224,7 +225,8 @@ blvacuumcleanup(IndexVacuumInfo *info, IndexBulkDeleteResult *stats)
 										MAIN_FORKNUM,
 										block_range_read_stream_cb,
 										&p,
-										0);
+										0,
+										NULL);
 
 	for (blkno = BLOOM_HEAD_BLKNO; blkno < npages; blkno++)
 	{
diff --git a/contrib/pg_prewarm/autoprewarm.c b/contrib/pg_prewarm/autoprewarm.c
index ba0bc8e6d4a..4c5448f177a 100644
--- a/contrib/pg_prewarm/autoprewarm.c
+++ b/contrib/pg_prewarm/autoprewarm.c
@@ -628,7 +628,8 @@ autoprewarm_database_main(Datum main_arg)
 												p.forknum,
 												apw_read_stream_next_block,
 												&p,
-												0);
+												0,
+												NULL);
 
 			/*
 			 * Loop until we've prewarmed all the blocks from this fork. The
diff --git a/contrib/pg_prewarm/pg_prewarm.c b/contrib/pg_prewarm/pg_prewarm.c
index c2716086693..e4541f46777 100644
--- a/contrib/pg_prewarm/pg_prewarm.c
+++ b/contrib/pg_prewarm/pg_prewarm.c
@@ -256,7 +256,8 @@ pg_prewarm(PG_FUNCTION_ARGS)
 											forkNumber,
 											block_range_read_stream_cb,
 											&p,
-											0);
+											0,
+											NULL);
 
 		for (block = first_block; block <= last_block; ++block)
 		{
diff --git a/contrib/pg_visibility/pg_visibility.c b/contrib/pg_visibility/pg_visibility.c
index dfab0b64cf5..f09bb68624f 100644
--- a/contrib/pg_visibility/pg_visibility.c
+++ b/contrib/pg_visibility/pg_visibility.c
@@ -519,7 +519,8 @@ collect_visibility_data(Oid relid, bool include_pd)
 											MAIN_FORKNUM,
 											block_range_read_stream_cb,
 											&p,
-											0);
+											0,
+											NULL);
 	}
 
 	for (blkno = 0; blkno < nblocks; ++blkno)
@@ -739,7 +740,8 @@ collect_corrupt_items(Oid relid, bool all_visible, bool all_frozen)
 										MAIN_FORKNUM,
 										collect_corrupt_items_read_stream_next_block,
 										&p,
-										0);
+										0,
+										NULL);
 
 	/* Loop over every block in the relation. */
 	while ((buffer = read_stream_next_buffer(stream, NULL)) != InvalidBuffer)
diff --git a/contrib/pgstattuple/pgstatapprox.c b/contrib/pgstattuple/pgstatapprox.c
index 21e0b50fb4b..a926a1f1168 100644
--- a/contrib/pgstattuple/pgstatapprox.c
+++ b/contrib/pgstattuple/pgstatapprox.c
@@ -146,7 +146,8 @@ statapprox_heap(Relation rel, output_type *stat)
 										MAIN_FORKNUM,
 										statapprox_heap_read_stream_next,
 										&p,
-										0);
+										0,
+										NULL);
 
 	for (;;)
 	{
diff --git a/contrib/pgstattuple/pgstatindex.c b/contrib/pgstattuple/pgstatindex.c
index 3a3f2637bd9..0de33558c47 100644
--- a/contrib/pgstattuple/pgstatindex.c
+++ b/contrib/pgstattuple/pgstatindex.c
@@ -296,7 +296,8 @@ pgstatindex_impl(Relation rel, FunctionCallInfo fcinfo)
 										MAIN_FORKNUM,
 										block_range_read_stream_cb,
 										&p,
-										0);
+										0,
+										NULL);
 
 	for (blkno = startblk; blkno < nblocks; blkno++)
 	{
@@ -685,7 +686,8 @@ pgstathashindex(PG_FUNCTION_ARGS)
 										MAIN_FORKNUM,
 										block_range_read_stream_cb,
 										&p,
-										0);
+										0,
+										NULL);
 
 	for (blkno = startblk; blkno < nblocks; blkno++)
 	{
diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index 2a0f8c8e3b8..eb4e4c5de4a 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -2192,7 +2192,8 @@ brin_vacuum_scan(Relation idxrel, BufferAccessStrategy strategy)
 										MAIN_FORKNUM,
 										block_range_read_stream_cb,
 										&p,
-										0);
+										0,
+										NULL);
 
 	/*
 	 * Scan the index in physical order, and clean up any possible mess in
diff --git a/src/backend/access/gin/ginvacuum.c b/src/backend/access/gin/ginvacuum.c
index 840543eb664..484360a9d6d 100644
--- a/src/backend/access/gin/ginvacuum.c
+++ b/src/backend/access/gin/ginvacuum.c
@@ -799,7 +799,8 @@ ginvacuumcleanup(IndexVacuumInfo *info, IndexBulkDeleteResult *stats)
 										MAIN_FORKNUM,
 										block_range_read_stream_cb,
 										&p,
-										0);
+										0,
+										NULL);
 
 	for (blkno = GIN_ROOT_BLKNO; blkno < npages; blkno++)
 	{
diff --git a/src/backend/access/gist/gistvacuum.c b/src/backend/access/gist/gistvacuum.c
index 686a0418054..e4f578b2de0 100644
--- a/src/backend/access/gist/gistvacuum.c
+++ b/src/backend/access/gist/gistvacuum.c
@@ -223,7 +223,8 @@ gistvacuumscan(IndexVacuumInfo *info, IndexBulkDeleteResult *stats,
 										MAIN_FORKNUM,
 										block_range_read_stream_cb,
 										&p,
-										0);
+										0,
+										NULL);
 	for (;;)
 	{
 		/* Get the current relation length */
diff --git a/src/backend/access/hash/hash.c b/src/backend/access/hash/hash.c
index 6df5e7ccbd1..043088dbee8 100644
--- a/src/backend/access/hash/hash.c
+++ b/src/backend/access/hash/hash.c
@@ -546,7 +546,8 @@ hashbulkdelete(IndexVacuumInfo *info, IndexBulkDeleteResult *stats,
 										MAIN_FORKNUM,
 										hash_bulkdelete_read_stream_cb,
 										&stream_private,
-										0);
+										0,
+										NULL);
 
 bucket_loop:
 	while (cur_bucket <= cur_maxbucket)
diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index 323badd27f1..6acbcf1f342 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -1267,6 +1267,9 @@ heap_beginscan(Relation relation, Snapshot snapshot,
 
 	scan->rs_read_stream = NULL;
 
+	/* reset the stats counters */
+	memset(&scan->rs_base.rs_iostats, 0, sizeof(IOStats));
+
 	/*
 	 * Set up a read stream for sequential scans and TID range scans. This
 	 * should be done after initscan() because initscan() allocates the
@@ -1296,7 +1299,8 @@ heap_beginscan(Relation relation, Snapshot snapshot,
 														  MAIN_FORKNUM,
 														  cb,
 														  scan,
-														  0);
+														  0,
+														  &scan->rs_base.rs_iostats);
 	}
 	else if (scan->rs_base.rs_flags & SO_TYPE_BITMAPSCAN)
 	{
@@ -1307,7 +1311,8 @@ heap_beginscan(Relation relation, Snapshot snapshot,
 														  MAIN_FORKNUM,
 														  bitmapheap_stream_read_next,
 														  scan,
-														  sizeof(TBMIterateResult));
+														  sizeof(TBMIterateResult),
+														  &scan->rs_base.rs_iostats);
 	}
 
 	scan->rs_vmbuffer = InvalidBuffer;
@@ -1416,38 +1421,6 @@ heap_endscan(TableScanDesc sscan)
 	pfree(scan);
 }
 
-/*
- * heap_scan_stats
- *		return stats collected by the read stream
- *
- * Returns NULL if the scan is not using a read stream.
- */
-TableScanStats
-heap_scan_stats(TableScanDesc sscan)
-{
-	HeapScanDesc scan = (HeapScanDesc) sscan;
-	IOStats stats;
-	TableScanStats res;
-
-	if (!scan->rs_read_stream)
-		return NULL;
-
-	stats = read_stream_prefetch_stats(scan->rs_read_stream);
-
-	res = palloc0(sizeof(TableScanStatsData));
-
-	res->prefetch_count = stats.prefetch_count;
-	res->distance_sum = stats.distance_sum;
-	res->distance_max = stats.distance_max;
-	res->distance_capacity = stats.distance_capacity;
-	res->stall_count = stats.stall_count;
-	res->io_count = stats.io_count;
-	res->io_nblocks = stats.io_nblocks;
-	res->io_in_progress = stats.io_in_progress;
-
-	return res;
-}
-
 HeapTuple
 heap_getnext(TableScanDesc sscan, ScanDirection direction)
 {
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 51ac2164afd..253a735b6c1 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -2650,7 +2650,6 @@ static const TableAmRoutine heapam_methods = {
 
 	.scan_set_tidrange = heap_set_tidrange,
 	.scan_getnextslot_tidrange = heap_getnextslot_tidrange,
-	.scan_stats = heap_scan_stats,
 
 	.parallelscan_estimate = table_block_parallelscan_estimate,
 	.parallelscan_initialize = table_block_parallelscan_initialize,
diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c
index 82c5b28e0ad..bec454de6d8 100644
--- a/src/backend/access/heap/vacuumlazy.c
+++ b/src/backend/access/heap/vacuumlazy.c
@@ -1290,7 +1290,8 @@ lazy_scan_heap(LVRelState *vacrel)
 										MAIN_FORKNUM,
 										heap_vac_scan_next_block,
 										vacrel,
-										sizeof(bool));
+										sizeof(bool),
+										NULL);
 
 	while (true)
 	{
@@ -2790,7 +2791,8 @@ lazy_vacuum_heap_rel(LVRelState *vacrel)
 										MAIN_FORKNUM,
 										vacuum_reap_lp_read_stream_next,
 										iter,
-										sizeof(TidStoreIterResult));
+										sizeof(TidStoreIterResult),
+										NULL);
 
 	while (true)
 	{
diff --git a/src/backend/access/nbtree/nbtree.c b/src/backend/access/nbtree/nbtree.c
index aed74590cf4..e5cd51cce4d 100644
--- a/src/backend/access/nbtree/nbtree.c
+++ b/src/backend/access/nbtree/nbtree.c
@@ -1325,7 +1325,8 @@ btvacuumscan(IndexVacuumInfo *info, IndexBulkDeleteResult *stats,
 										MAIN_FORKNUM,
 										block_range_read_stream_cb,
 										&p,
-										0);
+										0,
+										NULL);
 	for (;;)
 	{
 		/* Get the current relation length */
diff --git a/src/backend/access/spgist/spgvacuum.c b/src/backend/access/spgist/spgvacuum.c
index 6b7117b56b2..206a8e9c8b0 100644
--- a/src/backend/access/spgist/spgvacuum.c
+++ b/src/backend/access/spgist/spgvacuum.c
@@ -835,7 +835,8 @@ spgvacuumscan(spgBulkDeleteState *bds)
 										MAIN_FORKNUM,
 										block_range_read_stream_cb,
 										&p,
-										0);
+										0,
+										NULL);
 
 	/*
 	 * The outer loop iterates over all index pages except the metapage, in
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index eeed91be266..a2cf176a485 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -1251,7 +1251,8 @@ acquire_sample_rows(Relation onerel, int elevel,
 										MAIN_FORKNUM,
 										block_sampling_read_stream_next,
 										&bs,
-										0);
+										0,
+										NULL);
 
 	/* Outer loop over blocks to sample */
 	while (table_scan_analyze_next_block(scan, stream))
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 7a4009ae74f..25af6550098 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -3998,7 +3998,7 @@ show_tidbitmap_info(BitmapHeapScanState *planstate, ExplainState *es)
 }
 
 static void
-print_io_usage(ExplainState *es, TableScanStats stats)
+print_io_usage(ExplainState *es, IOStats *stats)
 {
 	/* don't print stats if there's nothing to report */
 	if (stats->prefetch_count > 0)
@@ -4067,8 +4067,7 @@ static void
 show_scan_io_usage(ScanState *planstate, ExplainState *es)
 {
 	Plan	   *plan = planstate->ps.plan;
-	TableScanStats	leader_stats;
-	TableScanStatsData	stats;
+	IOStats		stats;
 
 	if (!es->io)
 		return;
@@ -4078,16 +4077,7 @@ show_scan_io_usage(ScanState *planstate, ExplainState *es)
 		return;
 
 	/* collect prefetch statistics from the read stream */
-	leader_stats = table_scan_stats(planstate->ss_currentScanDesc);
-
-	if (leader_stats)
-	{
-		memcpy(&stats, leader_stats, sizeof(TableScanStatsData));
-	}
-	else
-	{
-		memset(&stats, 0, sizeof(TableScanStatsData));
-	}
+	stats = planstate->ss_currentScanDesc->rs_iostats;
 
 	/* Initialize counters with stats from the local process first */
 	switch (nodeTag(plan))
@@ -4146,9 +4136,6 @@ show_io_usage(PlanState *planstate, ExplainState *es, int worker)
 	Plan	   *plan = planstate->plan;
 	IOStats	   *stats = NULL;
 
-	// XXX
-	TableScanStatsData	tmp;
-
 	if (!es->io)
 		return;
 
@@ -4180,17 +4167,7 @@ show_io_usage(PlanState *planstate, ExplainState *es, int worker)
 			return;
 	}
 
-	/* XXX convert to TableScanStats */
-	tmp.distance_capacity = stats->distance_capacity;
-	tmp.distance_max = stats->distance_max;
-	tmp.distance_sum = stats->distance_sum;
-	tmp.io_count = stats->io_count;
-	tmp.io_in_progress = stats->io_in_progress;
-	tmp.io_nblocks = stats->io_nblocks;
-	tmp.prefetch_count = stats->prefetch_count;
-	tmp.stall_count = stats->stall_count;
-
-	print_io_usage(es, &tmp);
+	print_io_usage(es, stats);
 }
 
 /*
diff --git a/src/backend/executor/nodeBitmapHeapscan.c b/src/backend/executor/nodeBitmapHeapscan.c
index 0f1459f11a4..55b1e294d6c 100644
--- a/src/backend/executor/nodeBitmapHeapscan.c
+++ b/src/backend/executor/nodeBitmapHeapscan.c
@@ -315,7 +315,6 @@ ExecEndBitmapHeapScan(BitmapHeapScanState *node)
 	if (node->sinstrument != NULL && IsParallelWorker())
 	{
 		BitmapHeapScanInstrumentation *si;
-		TableScanStats stats;
 
 		Assert(ParallelWorkerNumber < node->sinstrument->num_workers);
 		si = &node->sinstrument->sinstrument[ParallelWorkerNumber];
@@ -331,10 +330,7 @@ ExecEndBitmapHeapScan(BitmapHeapScanState *node)
 		si->lossy_pages += node->stats.lossy_pages;
 
 		/* collect prefetch info for this process from the read_stream */
-		if ((stats = table_scan_stats(node->ss.ss_currentScanDesc)) != NULL)
-		{
-			ACCUMULATE_TABLE_STATS(stats, &si->io);
-		}
+		ACCUMULATE_TABLE_STATS(&node->ss.ss_currentScanDesc->rs_iostats, &si->io);
 	}
 
 	/*
diff --git a/src/backend/executor/nodeSeqscan.c b/src/backend/executor/nodeSeqscan.c
index 4fa85a1b6bc..28e33aa391d 100644
--- a/src/backend/executor/nodeSeqscan.c
+++ b/src/backend/executor/nodeSeqscan.c
@@ -303,7 +303,6 @@ ExecEndSeqScan(SeqScanState *node)
 	if (node->sinstrument != NULL && IsParallelWorker())
 	{
 		SeqScanInstrumentation *si;
-		TableScanStats stats;
 
 		Assert(ParallelWorkerNumber < node->sinstrument->num_workers);
 		si = &node->sinstrument->sinstrument[ParallelWorkerNumber];
@@ -317,10 +316,7 @@ ExecEndSeqScan(SeqScanState *node)
 		 */
 
 		/* collect prefetch info for this process from the read_stream */
-		if ((stats = table_scan_stats(node->ss.ss_currentScanDesc)) != NULL)
-		{
-			ACCUMULATE_TABLE_STATS(stats, &si->io);
-		}
+		ACCUMULATE_TABLE_STATS(&node->ss.ss_currentScanDesc->rs_iostats, &si->io);
 	}
 
 	/*
diff --git a/src/backend/storage/aio/read_stream.c b/src/backend/storage/aio/read_stream.c
index faa52ca4b2c..56b0b8e7fb5 100644
--- a/src/backend/storage/aio/read_stream.c
+++ b/src/backend/storage/aio/read_stream.c
@@ -109,7 +109,7 @@ struct ReadStream
 	bool		temporary;
 
 	/* scan stats counters */
-	IOStats		stats;
+	IOStats	   *stats;
 
 	/*
 	 * One-block buffer to support 'ungetting' a block number, to resolve flow
@@ -186,10 +186,15 @@ block_range_read_stream_cb(ReadStream *stream,
 static inline void
 read_stream_update_stats_prefetch(ReadStream *stream)
 {
-	stream->stats.prefetch_count++;
-	stream->stats.distance_sum += stream->pinned_buffers;
-	if (stream->pinned_buffers > stream->stats.distance_max)
-		stream->stats.distance_max = stream->pinned_buffers;
+	IOStats *stats = stream->stats;
+
+	if (stats == NULL)
+		return;
+
+	stats->prefetch_count++;
+	stats->distance_sum += stream->pinned_buffers;
+	if (stream->pinned_buffers > stats->distance_max)
+		stats->distance_max = stream->pinned_buffers;
 }
 
 /*
@@ -201,15 +206,25 @@ read_stream_update_stats_prefetch(ReadStream *stream)
 static inline void
 read_stream_update_stats_io(ReadStream *stream, int nblocks, int in_progress)
 {
-	stream->stats.io_count++;
-	stream->stats.io_nblocks += nblocks;
-	stream->stats.io_in_progress += in_progress;
+	IOStats *stats = stream->stats;
+
+	if (stats == NULL)
+		return;
+
+	stats->io_count++;
+	stats->io_nblocks += nblocks;
+	stats->io_in_progress += in_progress;
 }
 
 static inline void
 read_stream_update_stats_stall(ReadStream *stream)
 {
-	stream->stats.stall_count++;
+	IOStats *stats = stream->stats;
+
+	if (stats == NULL)
+		return;
+
+	stats->stall_count++;
 }
 
 /*
@@ -587,7 +602,8 @@ read_stream_begin_impl(int flags,
 					   ForkNumber forknum,
 					   ReadStreamBlockNumberCB callback,
 					   void *callback_private_data,
-					   size_t per_buffer_data_size)
+					   size_t per_buffer_data_size,
+					   IOStats *stats)
 {
 	ReadStream *stream;
 	size_t		size;
@@ -746,9 +762,10 @@ read_stream_begin_impl(int flags,
 	stream->seq_until_processed = InvalidBlockNumber;
 	stream->temporary = SmgrIsTemp(smgr);
 
-	/* zero the stats, then set capacity */
-	memset(&stream->stats, 0, sizeof(IOStats));
-	stream->stats.distance_capacity = max_pinned_buffers;
+	/* set capacity */
+	stream->stats = stats;
+	if (stream->stats)
+		stream->stats->distance_capacity = max_pinned_buffers;
 
 	/*
 	 * Skip the initial ramp-up phase if the caller says we're going to be
@@ -789,7 +806,8 @@ read_stream_begin_relation(int flags,
 						   ForkNumber forknum,
 						   ReadStreamBlockNumberCB callback,
 						   void *callback_private_data,
-						   size_t per_buffer_data_size)
+						   size_t per_buffer_data_size,
+						   IOStats *stats)
 {
 	return read_stream_begin_impl(flags,
 								  strategy,
@@ -799,7 +817,8 @@ read_stream_begin_relation(int flags,
 								  forknum,
 								  callback,
 								  callback_private_data,
-								  per_buffer_data_size);
+								  per_buffer_data_size,
+								  stats);
 }
 
 /*
@@ -824,7 +843,8 @@ read_stream_begin_smgr_relation(int flags,
 								  forknum,
 								  callback,
 								  callback_private_data,
-								  per_buffer_data_size);
+								  per_buffer_data_size,
+								  NULL);
 }
 
 /*
@@ -1179,10 +1199,3 @@ read_stream_end(ReadStream *stream)
 	read_stream_reset(stream);
 	pfree(stream);
 }
-
-/* return the prefetch stats for the read_stream */
-IOStats
-read_stream_prefetch_stats(ReadStream *stream)
-{
-	return stream->stats;
-}
diff --git a/src/include/access/heapam.h b/src/include/access/heapam.h
index 81dc0b7521d..2fdc50b865b 100644
--- a/src/include/access/heapam.h
+++ b/src/include/access/heapam.h
@@ -359,7 +359,6 @@ extern void heap_prepare_pagescan(TableScanDesc sscan);
 extern void heap_rescan(TableScanDesc sscan, ScanKey key, bool set_params,
 						bool allow_strat, bool allow_sync, bool allow_pagemode);
 extern void heap_endscan(TableScanDesc sscan);
-extern TableScanStats heap_scan_stats(TableScanDesc sscan);
 extern HeapTuple heap_getnext(TableScanDesc sscan, ScanDirection direction);
 extern bool heap_getnextslot(TableScanDesc sscan,
 							 ScanDirection direction, TupleTableSlot *slot);
diff --git a/src/include/access/relscan.h b/src/include/access/relscan.h
index c0488987b2d..e41f0f8ee13 100644
--- a/src/include/access/relscan.h
+++ b/src/include/access/relscan.h
@@ -16,6 +16,7 @@
 
 #include "access/htup_details.h"
 #include "access/itup.h"
+#include "executor/instrument_node.h"
 #include "nodes/tidbitmap.h"
 #include "port/atomics.h"
 #include "storage/relfilelocator.h"
@@ -62,6 +63,11 @@ typedef struct TableScanDescData
 	 */
 	uint32		rs_flags;
 
+	/*
+	 * IO statistics for the scan, optionally filled by the table AM.
+	 */
+	IOStats		rs_iostats;
+
 	struct ParallelTableScanDescData *rs_parallel;	/* parallel scan
 													 * information */
 } TableScanDescData;
diff --git a/src/include/access/tableam.h b/src/include/access/tableam.h
index ab84b76443c..06084752245 100644
--- a/src/include/access/tableam.h
+++ b/src/include/access/tableam.h
@@ -380,11 +380,6 @@ typedef struct TableAmRoutine
 											  ScanDirection direction,
 											  TupleTableSlot *slot);
 
-	/*
-	 * Collect statistics about table scan.
-	 */
-	TableScanStats		(*scan_stats) (TableScanDesc scan);
-
 	/* ------------------------------------------------------------------------
 	 * Parallel table scan related functions.
 	 * ------------------------------------------------------------------------
@@ -1011,18 +1006,6 @@ table_endscan(TableScanDesc scan)
 	scan->rs_rd->rd_tableam->scan_end(scan);
 }
 
-/*
- * Fetch statistics about table scan.
- */
-static inline TableScanStats
-table_scan_stats(TableScanDesc scan)
-{
-	if (scan->rs_rd->rd_tableam->scan_stats)
-		return scan->rs_rd->rd_tableam->scan_stats(scan);
-
-	return NULL;
-}
-
 /*
  * Restart a relation scan.
  */
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 96a8a75031f..f287f8fc813 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -1644,7 +1644,6 @@ typedef struct SeqScanState
 {
 	ScanState	ss;				/* its first field is NodeTag */
 	Size		pscan_len;		/* size of parallel heap scan descriptor */
-	SeqScanInstrumentation	stats;
 	SharedSeqScanInstrumentation *sinstrument;
 } SeqScanState;
 
diff --git a/src/include/storage/read_stream.h b/src/include/storage/read_stream.h
index 0676bdc9b14..b56bf925fc1 100644
--- a/src/include/storage/read_stream.h
+++ b/src/include/storage/read_stream.h
@@ -88,7 +88,8 @@ extern ReadStream *read_stream_begin_relation(int flags,
 											  ForkNumber forknum,
 											  ReadStreamBlockNumberCB callback,
 											  void *callback_private_data,
-											  size_t per_buffer_data_size);
+											  size_t per_buffer_data_size,
+											  IOStats *stat);
 extern Buffer read_stream_next_buffer(ReadStream *stream, void **per_buffer_data);
 extern BlockNumber read_stream_next_block(ReadStream *stream,
 										  BufferAccessStrategy *strategy);
@@ -105,6 +106,4 @@ extern void read_stream_resume(ReadStream *stream);
 extern void read_stream_reset(ReadStream *stream);
 extern void read_stream_end(ReadStream *stream);
 
-extern IOStats read_stream_prefetch_stats(ReadStream *stream);
-
 #endif							/* READ_STREAM_H */
-- 
2.53.0

