From 153ab2799803dc402789d0aa825456ea12f2d3d9 Mon Sep 17 00:00:00 2001
From: alterego655 <824662526@qq.com>
Date: Sun, 12 Oct 2025 21:27:22 +0800
Subject: [PATCH] pgstattuple: Use streaming read API in pgstatindex functions

Replace synchronous ReadBufferExtended() loops with the streaming read
API in pgstatindex_impl() and pgstathashindex().
---
 contrib/pgstattuple/pgstatindex.c | 205 ++++++++++++++++++------------
 1 file changed, 121 insertions(+), 84 deletions(-)

diff --git a/contrib/pgstattuple/pgstatindex.c b/contrib/pgstattuple/pgstatindex.c
index 40823d54fca..a708cc417b0 100644
--- a/contrib/pgstattuple/pgstatindex.c
+++ b/contrib/pgstattuple/pgstatindex.c
@@ -37,6 +37,7 @@
 #include "funcapi.h"
 #include "miscadmin.h"
 #include "storage/bufmgr.h"
+#include "storage/read_stream.h"
 #include "utils/rel.h"
 #include "utils/varlena.h"
 
@@ -273,60 +274,77 @@ pgstatindex_impl(Relation rel, FunctionCallInfo fcinfo)
 	indexStat.fragments = 0;
 
 	/*
-	 * Scan all blocks except the metapage
+	 * Scan all blocks except the metapage using streaming reads
 	 */
 	nblocks = RelationGetNumberOfBlocks(rel);
 
-	for (blkno = 1; blkno < nblocks; blkno++)
 	{
-		Buffer		buffer;
-		Page		page;
-		BTPageOpaque opaque;
-
-		CHECK_FOR_INTERRUPTS();
-
-		/* Read and lock buffer */
-		buffer = ReadBufferExtended(rel, MAIN_FORKNUM, blkno, RBM_NORMAL, bstrategy);
-		LockBuffer(buffer, BUFFER_LOCK_SHARE);
-
-		page = BufferGetPage(buffer);
-		opaque = BTPageGetOpaque(page);
-
-		/*
-		 * Determine page type, and update totals.
-		 *
-		 * Note that we arbitrarily bucket deleted pages together without
-		 * considering if they're leaf pages or internal pages.
-		 */
-		if (P_ISDELETED(opaque))
-			indexStat.deleted_pages++;
-		else if (P_IGNORE(opaque))
-			indexStat.empty_pages++;	/* this is the "half dead" state */
-		else if (P_ISLEAF(opaque))
+		BlockRangeReadStreamPrivate p;
+		ReadStream *stream;
+
+		p.current_blocknum = 1;
+		p.last_exclusive = nblocks;
+
+		stream = read_stream_begin_relation(READ_STREAM_FULL |
+											READ_STREAM_USE_BATCHING,
+											bstrategy,
+											rel,
+											MAIN_FORKNUM,
+											block_range_read_stream_cb,
+											&p,
+											0);
+
+		for (blkno = 1; blkno < nblocks; blkno++)
 		{
-			int			max_avail;
+			Buffer		buffer;
+			Page		page;
+			BTPageOpaque opaque;
 
-			max_avail = BLCKSZ - (BLCKSZ - ((PageHeader) page)->pd_special + SizeOfPageHeaderData);
-			indexStat.max_avail += max_avail;
-			indexStat.free_space += PageGetExactFreeSpace(page);
+			CHECK_FOR_INTERRUPTS();
 
-			indexStat.leaf_pages++;
+			buffer = read_stream_next_buffer(stream, NULL);
+			LockBuffer(buffer, BUFFER_LOCK_SHARE);
+
+			page = BufferGetPage(buffer);
+			opaque = BTPageGetOpaque(page);
 
 			/*
-			 * If the next leaf is on an earlier block, it means a
-			 * fragmentation.
+			 * Determine page type, and update totals.
+			 *
+			 * Note that we arbitrarily bucket deleted pages together without
+			 * considering if they're leaf pages or internal pages.
 			 */
-			if (opaque->btpo_next != P_NONE && opaque->btpo_next < blkno)
-				indexStat.fragments++;
-		}
-		else
-			indexStat.internal_pages++;
+			if (P_ISDELETED(opaque))
+				indexStat.deleted_pages++;
+			else if (P_IGNORE(opaque))
+				indexStat.empty_pages++;	/* this is the "half dead" state */
+			else if (P_ISLEAF(opaque))
+			{
+				int			max_avail;
 
-		/* Unlock and release buffer */
-		LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
-		ReleaseBuffer(buffer);
+				max_avail = BLCKSZ - (BLCKSZ - ((PageHeader) page)->pd_special + SizeOfPageHeaderData);
+				indexStat.max_avail += max_avail;
+				indexStat.free_space += PageGetExactFreeSpace(page);
+
+				indexStat.leaf_pages++;
+
+				/*
+				 * If the next leaf is on an earlier block, it means a
+				 * fragmentation.
+				 */
+				if (opaque->btpo_next != P_NONE && opaque->btpo_next < blkno)
+					indexStat.fragments++;
+			}
+			else
+				indexStat.internal_pages++;
+
+		UnlockReleaseBuffer(buffer);
 	}
 
+	Assert(read_stream_next_buffer(stream, NULL) == InvalidBuffer);
+	read_stream_end(stream);
+}
+
 	relation_close(rel, AccessShareLock);
 
 	/*----------------------------
@@ -636,60 +654,79 @@ pgstathashindex(PG_FUNCTION_ARGS)
 	/* prepare access strategy for this index */
 	bstrategy = GetAccessStrategy(BAS_BULKREAD);
 
-	/* Start from blkno 1 as 0th block is metapage */
-	for (blkno = 1; blkno < nblocks; blkno++)
+	/* Scan all blocks except the metapage using streaming reads */
 	{
-		Buffer		buf;
-		Page		page;
-
-		CHECK_FOR_INTERRUPTS();
-
-		buf = ReadBufferExtended(rel, MAIN_FORKNUM, blkno, RBM_NORMAL,
-								 bstrategy);
-		LockBuffer(buf, BUFFER_LOCK_SHARE);
-		page = BufferGetPage(buf);
-
-		if (PageIsNew(page))
-			stats.unused_pages++;
-		else if (PageGetSpecialSize(page) !=
-				 MAXALIGN(sizeof(HashPageOpaqueData)))
-			ereport(ERROR,
-					(errcode(ERRCODE_INDEX_CORRUPTED),
-					 errmsg("index \"%s\" contains corrupted page at block %u",
-							RelationGetRelationName(rel),
-							BufferGetBlockNumber(buf))));
-		else
+		BlockRangeReadStreamPrivate p;
+		ReadStream *stream;
+
+		p.current_blocknum = 1;
+		p.last_exclusive = nblocks;
+
+		stream = read_stream_begin_relation(READ_STREAM_FULL |
+											READ_STREAM_USE_BATCHING,
+											bstrategy,
+											rel,
+											MAIN_FORKNUM,
+											block_range_read_stream_cb,
+											&p,
+											0);
+
+		for (blkno = 1; blkno < nblocks; blkno++)
 		{
-			HashPageOpaque opaque;
-			int			pagetype;
+			Buffer		buf;
+			Page		page;
 
-			opaque = HashPageGetOpaque(page);
-			pagetype = opaque->hasho_flag & LH_PAGE_TYPE;
+			CHECK_FOR_INTERRUPTS();
 
-			if (pagetype == LH_BUCKET_PAGE)
-			{
-				stats.bucket_pages++;
-				GetHashPageStats(page, &stats);
-			}
-			else if (pagetype == LH_OVERFLOW_PAGE)
-			{
-				stats.overflow_pages++;
-				GetHashPageStats(page, &stats);
-			}
-			else if (pagetype == LH_BITMAP_PAGE)
-				stats.bitmap_pages++;
-			else if (pagetype == LH_UNUSED_PAGE)
+			buf = read_stream_next_buffer(stream, NULL);
+			LockBuffer(buf, BUFFER_LOCK_SHARE);
+			page = BufferGetPage(buf);
+
+			if (PageIsNew(page))
 				stats.unused_pages++;
-			else
+			else if (PageGetSpecialSize(page) !=
+					 MAXALIGN(sizeof(HashPageOpaqueData)))
 				ereport(ERROR,
 						(errcode(ERRCODE_INDEX_CORRUPTED),
-						 errmsg("unexpected page type 0x%04X in HASH index \"%s\" block %u",
-								opaque->hasho_flag, RelationGetRelationName(rel),
+						 errmsg("index \"%s\" contains corrupted page at block %u",
+								RelationGetRelationName(rel),
 								BufferGetBlockNumber(buf))));
-		}
+			else
+			{
+				HashPageOpaque opaque;
+				int			pagetype;
+
+				opaque = HashPageGetOpaque(page);
+				pagetype = opaque->hasho_flag & LH_PAGE_TYPE;
+
+				if (pagetype == LH_BUCKET_PAGE)
+				{
+					stats.bucket_pages++;
+					GetHashPageStats(page, &stats);
+				}
+				else if (pagetype == LH_OVERFLOW_PAGE)
+				{
+					stats.overflow_pages++;
+					GetHashPageStats(page, &stats);
+				}
+				else if (pagetype == LH_BITMAP_PAGE)
+					stats.bitmap_pages++;
+				else if (pagetype == LH_UNUSED_PAGE)
+					stats.unused_pages++;
+				else
+					ereport(ERROR,
+							(errcode(ERRCODE_INDEX_CORRUPTED),
+							 errmsg("unexpected page type 0x%04X in HASH index \"%s\" block %u",
+									opaque->hasho_flag, RelationGetRelationName(rel),
+									BufferGetBlockNumber(buf))));
+			}
 		UnlockReleaseBuffer(buf);
 	}
 
+	Assert(read_stream_next_buffer(stream, NULL) == InvalidBuffer);
+	read_stream_end(stream);
+}
+
 	/* Done accessing the index */
 	index_close(rel, AccessShareLock);
 
-- 
2.51.0

