0001-Add-separate-record-leaf-and-metadata-stats-for-inde.patch

text/plain

Filename: 0001-Add-separate-record-leaf-and-metadata-stats-for-inde.patch
Type: text/plain
Part: 0
Message: Re: Metadata and record block access stats for indexes
From 74273804d72b3b80e13023c6526bcdf87bc4889b Mon Sep 17 00:00:00 2001
From: Mircea Cadariu <cadariu.mircea@gmail.com>
Date: Fri, 11 Apr 2025 20:30:31 +0100
Subject: [PATCH] Add separate record (leaf) and metadata stats for index
 buffers in the system views.

To achieve this, we pass on a boolean flag from the index code to the bufmgr.
We use this back in the index code and update the counters accordingly depending
on whether it's a metadata or record block it's just read.
---
 contrib/amcheck/verify_gin.c                 |   6 +-
 contrib/amcheck/verify_nbtree.c              |   6 +-
 contrib/bloom/blinsert.c                     |   6 +-
 contrib/bloom/blscan.c                       |   2 +-
 contrib/bloom/blutils.c                      |   6 +-
 contrib/bloom/blvacuum.c                     |   6 +-
 contrib/pageinspect/btreefuncs.c             |   8 +-
 contrib/pageinspect/rawpage.c                |   2 +-
 contrib/pg_surgery/heap_surgery.c            |   2 +-
 contrib/pg_visibility/pg_visibility.c        |   2 +-
 contrib/pgstattuple/pgstatapprox.c           |   2 +-
 contrib/pgstattuple/pgstatindex.c            |   8 +-
 contrib/pgstattuple/pgstattuple.c            |   8 +-
 doc/src/sgml/monitoring.sgml                 | 110 ++++++++++++++
 src/backend/access/brin/brin.c               |   6 +-
 src/backend/access/brin/brin_pageops.c       |   8 +-
 src/backend/access/brin/brin_revmap.c        |  24 +++-
 src/backend/access/gin/ginbtree.c            |  20 ++-
 src/backend/access/gin/ginfast.c             |  25 +++-
 src/backend/access/gin/ginget.c              |  12 +-
 src/backend/access/gin/ginutil.c             |  13 +-
 src/backend/access/gin/ginvacuum.c           |  22 +--
 src/backend/access/gist/gist.c               |  23 ++-
 src/backend/access/gist/gistbuild.c          |  23 ++-
 src/backend/access/gist/gistget.c            |   8 +-
 src/backend/access/gist/gistutil.c           |   5 +-
 src/backend/access/gist/gistvacuum.c         |   6 +-
 src/backend/access/hash/hash.c               |   5 +-
 src/backend/access/hash/hashpage.c           |  21 ++-
 src/backend/access/heap/heapam.c             |  16 +--
 src/backend/access/heap/heapam_handler.c     |   6 +-
 src/backend/access/heap/hio.c                |   8 +-
 src/backend/access/heap/vacuumlazy.c         |   2 +-
 src/backend/access/heap/visibilitymap.c      |   2 +-
 src/backend/access/nbtree/nbtinsert.c        |  35 +++--
 src/backend/access/nbtree/nbtpage.c          |  67 ++++++---
 src/backend/access/nbtree/nbtree.c           |   2 +-
 src/backend/access/nbtree/nbtsearch.c        |  34 +++--
 src/backend/access/nbtree/nbtutils.c         |   5 +-
 src/backend/access/spgist/spgdoinsert.c      |   8 +-
 src/backend/access/spgist/spgscan.c          |   8 +-
 src/backend/access/spgist/spgutils.c         |  17 ++-
 src/backend/access/spgist/spgvacuum.c        |   2 +-
 src/backend/access/transam/xloginsert.c      |   2 +-
 src/backend/catalog/system_views.sql         |  30 +++-
 src/backend/commands/sequence.c              |   2 +-
 src/backend/storage/aio/read_stream.c        |   6 +-
 src/backend/storage/buffer/bufmgr.c          |  54 ++++---
 src/backend/storage/freespace/freespace.c    |   2 +-
 src/backend/utils/activity/pgstat_database.c |   4 +
 src/backend/utils/activity/pgstat_relation.c |   8 ++
 src/backend/utils/adt/pgstatfuncs.c          |  24 ++++
 src/include/access/nbtree.h                  |   4 +-
 src/include/catalog/pg_proc.dat              |  32 +++++
 src/include/pgstat.h                         |  43 ++++++
 src/include/storage/bufmgr.h                 |  12 +-
 src/test/modules/test_aio/test_aio.c         |   4 +-
 src/test/regress/expected/rules.out          |  40 +++++-
 src/test/regress/expected/stats.out          | 144 +++++++++++++++++++
 src/test/regress/sql/stats.sql               |  94 ++++++++++++
 60 files changed, 910 insertions(+), 202 deletions(-)

diff --git a/contrib/amcheck/verify_gin.c b/contrib/amcheck/verify_gin.c
index 318fe33051..10d4908234 100644
--- a/contrib/amcheck/verify_gin.c
+++ b/contrib/amcheck/verify_gin.c
@@ -173,7 +173,7 @@ gin_check_posting_tree_parent_keys_consistency(Relation rel, BlockNumber posting
 		CHECK_FOR_INTERRUPTS();
 
 		buffer = ReadBufferExtended(rel, MAIN_FORKNUM, stack->blkno,
-									RBM_NORMAL, strategy);
+									RBM_NORMAL, strategy, NULL);
 		LockBuffer(buffer, GIN_SHARE);
 		page = (Page) BufferGetPage(buffer);
 
@@ -439,7 +439,7 @@ gin_check_parent_keys_consistency(Relation rel,
 		CHECK_FOR_INTERRUPTS();
 
 		buffer = ReadBufferExtended(rel, MAIN_FORKNUM, stack->blkno,
-									RBM_NORMAL, strategy);
+									RBM_NORMAL, strategy, NULL);
 		LockBuffer(buffer, GIN_SHARE);
 		page = (Page) BufferGetPage(buffer);
 		lsn = BufferGetLSNAtomic(buffer);
@@ -732,7 +732,7 @@ gin_refind_parent(Relation rel, BlockNumber parentblkno,
 	IndexTuple	result = NULL;
 
 	parentbuf = ReadBufferExtended(rel, MAIN_FORKNUM, parentblkno, RBM_NORMAL,
-								   strategy);
+								   strategy, NULL);
 
 	LockBuffer(parentbuf, GIN_SHARE);
 	parentpage = BufferGetPage(parentbuf);
diff --git a/contrib/amcheck/verify_nbtree.c b/contrib/amcheck/verify_nbtree.c
index f11c43a0ed..5cf51513e2 100644
--- a/contrib/amcheck/verify_nbtree.c
+++ b/contrib/amcheck/verify_nbtree.c
@@ -1125,7 +1125,7 @@ bt_recheck_sibling_links(BtreeCheckState *state,
 
 		/* Couple locks in the usual order for nbtree:  Left to right */
 		lbuf = ReadBufferExtended(state->rel, MAIN_FORKNUM, leftcurrent,
-								  RBM_NORMAL, state->checkstrategy);
+								  RBM_NORMAL, state->checkstrategy, NULL);
 		LockBuffer(lbuf, BT_READ);
 		_bt_checkpage(state->rel, lbuf);
 		page = BufferGetPage(lbuf);
@@ -1149,7 +1149,7 @@ bt_recheck_sibling_links(BtreeCheckState *state,
 		{
 			newtargetbuf = ReadBufferExtended(state->rel, MAIN_FORKNUM,
 											  newtargetblock, RBM_NORMAL,
-											  state->checkstrategy);
+											  state->checkstrategy, NULL);
 			LockBuffer(newtargetbuf, BT_READ);
 			_bt_checkpage(state->rel, newtargetbuf);
 			page = BufferGetPage(newtargetbuf);
@@ -3315,7 +3315,7 @@ palloc_btree_page(BtreeCheckState *state, BlockNumber blocknum)
 	 * longer than we must.
 	 */
 	buffer = ReadBufferExtended(state->rel, MAIN_FORKNUM, blocknum, RBM_NORMAL,
-								state->checkstrategy);
+								state->checkstrategy, NULL);
 	LockBuffer(buffer, BT_READ);
 
 	/*
diff --git a/contrib/bloom/blinsert.c b/contrib/bloom/blinsert.c
index 7866438122..dd7c0b9b10 100644
--- a/contrib/bloom/blinsert.c
+++ b/contrib/bloom/blinsert.c
@@ -204,7 +204,7 @@ blinsert(Relation index, Datum *values, bool *isnull,
 	 * At first, try to insert new tuple to the first page in notFullPage
 	 * array.  If successful, we don't need to modify the meta page.
 	 */
-	metaBuffer = ReadBuffer(index, BLOOM_METAPAGE_BLKNO);
+	metaBuffer = ReadBuffer(index, BLOOM_METAPAGE_BLKNO, NULL);
 	LockBuffer(metaBuffer, BUFFER_LOCK_SHARE);
 	metaData = BloomPageGetMeta(BufferGetPage(metaBuffer));
 
@@ -216,7 +216,7 @@ blinsert(Relation index, Datum *values, bool *isnull,
 		/* Don't hold metabuffer lock while doing insert */
 		LockBuffer(metaBuffer, BUFFER_LOCK_UNLOCK);
 
-		buffer = ReadBuffer(index, blkno);
+		buffer = ReadBuffer(index, blkno, NULL);
 		LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
 
 		state = GenericXLogStart(index);
@@ -283,7 +283,7 @@ blinsert(Relation index, Datum *values, bool *isnull,
 		blkno = metaData->notFullPage[nStart];
 		Assert(blkno != InvalidBlockNumber);
 
-		buffer = ReadBuffer(index, blkno);
+		buffer = ReadBuffer(index, blkno, NULL);
 		LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
 		page = GenericXLogRegisterBuffer(state, buffer, 0);
 
diff --git a/contrib/bloom/blscan.c b/contrib/bloom/blscan.c
index d072f47fe2..17b920ffd2 100644
--- a/contrib/bloom/blscan.c
+++ b/contrib/bloom/blscan.c
@@ -125,7 +125,7 @@ blgetbitmap(IndexScanDesc scan, TIDBitmap *tbm)
 		Page		page;
 
 		buffer = ReadBufferExtended(scan->indexRelation, MAIN_FORKNUM,
-									blkno, RBM_NORMAL, bas);
+									blkno, RBM_NORMAL, bas, NULL);
 
 		LockBuffer(buffer, BUFFER_LOCK_SHARE);
 		page = BufferGetPage(buffer);
diff --git a/contrib/bloom/blutils.c b/contrib/bloom/blutils.c
index 2c0e71eedc..a17fa0b99a 100644
--- a/contrib/bloom/blutils.c
+++ b/contrib/bloom/blutils.c
@@ -188,7 +188,7 @@ initBloomState(BloomState *state, Relation index)
 
 		opts = MemoryContextAlloc(index->rd_indexcxt, sizeof(BloomOptions));
 
-		buffer = ReadBuffer(index, BLOOM_METAPAGE_BLKNO);
+		buffer = ReadBuffer(index, BLOOM_METAPAGE_BLKNO, NULL);
 		LockBuffer(buffer, BUFFER_LOCK_SHARE);
 
 		page = BufferGetPage(buffer);
@@ -367,7 +367,7 @@ BloomNewBuffer(Relation index)
 		if (blkno == InvalidBlockNumber)
 			break;
 
-		buffer = ReadBuffer(index, blkno);
+		buffer = ReadBuffer(index, blkno, NULL);
 
 		/*
 		 * We have to guard against the possibility that someone else already
@@ -459,7 +459,7 @@ BloomInitMetapage(Relation index, ForkNumber forknum)
 	 * block number 0 (BLOOM_METAPAGE_BLKNO).  No need to hold the extension
 	 * lock because there cannot be concurrent inserters yet.
 	 */
-	metaBuffer = ReadBufferExtended(index, forknum, P_NEW, RBM_NORMAL, NULL);
+	metaBuffer = ReadBufferExtended(index, forknum, P_NEW, RBM_NORMAL, NULL, NULL);
 	LockBuffer(metaBuffer, BUFFER_LOCK_EXCLUSIVE);
 	Assert(BufferGetBlockNumber(metaBuffer) == BLOOM_METAPAGE_BLKNO);
 
diff --git a/contrib/bloom/blvacuum.c b/contrib/bloom/blvacuum.c
index 86b15a75f6..ebe769d375 100644
--- a/contrib/bloom/blvacuum.c
+++ b/contrib/bloom/blvacuum.c
@@ -60,7 +60,7 @@ blbulkdelete(IndexVacuumInfo *info, IndexBulkDeleteResult *stats,
 		vacuum_delay_point(false);
 
 		buffer = ReadBufferExtended(index, MAIN_FORKNUM, blkno,
-									RBM_NORMAL, info->strategy);
+									RBM_NORMAL, info->strategy, NULL);
 
 		LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
 		gxlogState = GenericXLogStart(index);
@@ -139,7 +139,7 @@ blbulkdelete(IndexVacuumInfo *info, IndexBulkDeleteResult *stats,
 	 * info could already be out of date at this point, but blinsert() will
 	 * cope if so.
 	 */
-	buffer = ReadBuffer(index, BLOOM_METAPAGE_BLKNO);
+	buffer = ReadBuffer(index, BLOOM_METAPAGE_BLKNO, NULL);
 	LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
 
 	gxlogState = GenericXLogStart(index);
@@ -190,7 +190,7 @@ blvacuumcleanup(IndexVacuumInfo *info, IndexBulkDeleteResult *stats)
 		vacuum_delay_point(false);
 
 		buffer = ReadBufferExtended(index, MAIN_FORKNUM, blkno,
-									RBM_NORMAL, info->strategy);
+									RBM_NORMAL, info->strategy, NULL);
 		LockBuffer(buffer, BUFFER_LOCK_SHARE);
 		page = (Page) BufferGetPage(buffer);
 
diff --git a/contrib/pageinspect/btreefuncs.c b/contrib/pageinspect/btreefuncs.c
index 294821231f..f23a219098 100644
--- a/contrib/pageinspect/btreefuncs.c
+++ b/contrib/pageinspect/btreefuncs.c
@@ -280,7 +280,7 @@ bt_page_stats_internal(PG_FUNCTION_ARGS, enum pageinspect_version ext_version)
 
 	bt_index_block_validate(rel, blkno);
 
-	buffer = ReadBuffer(rel, blkno);
+	buffer = ReadBuffer(rel, blkno, NULL);
 	LockBuffer(buffer, BUFFER_LOCK_SHARE);
 
 	/* keep compiler quiet */
@@ -420,7 +420,7 @@ bt_multi_page_stats(PG_FUNCTION_ARGS)
 		BTPageStat	stat;
 		TupleDesc	tupleDesc;
 
-		buffer = ReadBuffer(rel, uargs->blkno);
+		buffer = ReadBuffer(rel, uargs->blkno, NULL);
 		LockBuffer(buffer, BUFFER_LOCK_SHARE);
 
 		/* keep compiler quiet */
@@ -649,7 +649,7 @@ bt_page_items_internal(PG_FUNCTION_ARGS, enum pageinspect_version ext_version)
 
 		bt_index_block_validate(rel, blkno);
 
-		buffer = ReadBuffer(rel, blkno);
+		buffer = ReadBuffer(rel, blkno, NULL);
 		LockBuffer(buffer, BUFFER_LOCK_SHARE);
 
 		/*
@@ -873,7 +873,7 @@ bt_metap(PG_FUNCTION_ARGS)
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 				 errmsg("cannot access temporary tables of other sessions")));
 
-	buffer = ReadBuffer(rel, 0);
+	buffer = ReadBuffer(rel, 0, NULL);
 	LockBuffer(buffer, BUFFER_LOCK_SHARE);
 
 	page = BufferGetPage(buffer);
diff --git a/contrib/pageinspect/rawpage.c b/contrib/pageinspect/rawpage.c
index 0d57123aa2..5bfb5072b3 100644
--- a/contrib/pageinspect/rawpage.c
+++ b/contrib/pageinspect/rawpage.c
@@ -188,7 +188,7 @@ get_raw_page_internal(text *relname, ForkNumber forknum, BlockNumber blkno)
 
 	/* Take a verbatim copy of the page */
 
-	buf = ReadBufferExtended(rel, forknum, blkno, RBM_NORMAL, NULL);
+	buf = ReadBufferExtended(rel, forknum, blkno, RBM_NORMAL, NULL, NULL);
 	LockBuffer(buf, BUFFER_LOCK_SHARE);
 
 	memcpy(raw_page_data, BufferGetPage(buf), BLCKSZ);
diff --git a/contrib/pg_surgery/heap_surgery.c b/contrib/pg_surgery/heap_surgery.c
index 3e86283beb..255f73460d 100644
--- a/contrib/pg_surgery/heap_surgery.c
+++ b/contrib/pg_surgery/heap_surgery.c
@@ -175,7 +175,7 @@ heap_force_common(FunctionCallInfo fcinfo, HeapTupleForceOption heap_force_opt)
 			continue;
 		}
 
-		buf = ReadBuffer(rel, blkno);
+		buf = ReadBuffer(rel, blkno, NULL);
 		LockBufferForCleanup(buf);
 
 		page = BufferGetPage(buf);
diff --git a/contrib/pg_visibility/pg_visibility.c b/contrib/pg_visibility/pg_visibility.c
index d79ef35006..45d827d3ba 100644
--- a/contrib/pg_visibility/pg_visibility.c
+++ b/contrib/pg_visibility/pg_visibility.c
@@ -154,7 +154,7 @@ pg_visibility(PG_FUNCTION_ARGS)
 	/* Here we have to explicitly check rel size ... */
 	if (blkno < RelationGetNumberOfBlocks(rel))
 	{
-		buffer = ReadBuffer(rel, blkno);
+		buffer = ReadBuffer(rel, blkno, NULL);
 		LockBuffer(buffer, BUFFER_LOCK_SHARE);
 
 		page = BufferGetPage(buffer);
diff --git a/contrib/pgstattuple/pgstatapprox.c b/contrib/pgstattuple/pgstatapprox.c
index a59ff4e9d4..46efc49522 100644
--- a/contrib/pgstattuple/pgstatapprox.c
+++ b/contrib/pgstattuple/pgstatapprox.c
@@ -94,7 +94,7 @@ statapprox_heap(Relation rel, output_type *stat)
 		}
 
 		buf = ReadBufferExtended(rel, MAIN_FORKNUM, blkno,
-								 RBM_NORMAL, bstrategy);
+								 RBM_NORMAL, bstrategy, NULL);
 
 		LockBuffer(buf, BUFFER_LOCK_SHARE);
 
diff --git a/contrib/pgstattuple/pgstatindex.c b/contrib/pgstattuple/pgstatindex.c
index 4b9d76ec4e..2f6b4bbe8c 100644
--- a/contrib/pgstattuple/pgstatindex.c
+++ b/contrib/pgstattuple/pgstatindex.c
@@ -250,7 +250,7 @@ pgstatindex_impl(Relation rel, FunctionCallInfo fcinfo)
 	 * Read metapage
 	 */
 	{
-		Buffer		buffer = ReadBufferExtended(rel, MAIN_FORKNUM, 0, RBM_NORMAL, bstrategy);
+		Buffer		buffer = ReadBufferExtended(rel, MAIN_FORKNUM, 0, RBM_NORMAL, bstrategy, NULL);
 		Page		page = BufferGetPage(buffer);
 		BTMetaPageData *metad = BTPageGetMeta(page);
 
@@ -286,7 +286,7 @@ pgstatindex_impl(Relation rel, FunctionCallInfo fcinfo)
 		CHECK_FOR_INTERRUPTS();
 
 		/* Read and lock buffer */
-		buffer = ReadBufferExtended(rel, MAIN_FORKNUM, blkno, RBM_NORMAL, bstrategy);
+		buffer = ReadBufferExtended(rel, MAIN_FORKNUM, blkno, RBM_NORMAL, bstrategy, NULL);
 		LockBuffer(buffer, BUFFER_LOCK_SHARE);
 
 		page = BufferGetPage(buffer);
@@ -542,7 +542,7 @@ pgstatginindex_internal(Oid relid, FunctionCallInfo fcinfo)
 	/*
 	 * Read metapage
 	 */
-	buffer = ReadBuffer(rel, GIN_METAPAGE_BLKNO);
+	buffer = ReadBuffer(rel, GIN_METAPAGE_BLKNO, NULL);
 	LockBuffer(buffer, GIN_SHARE);
 	page = BufferGetPage(buffer);
 	metadata = GinPageGetMeta(page);
@@ -645,7 +645,7 @@ pgstathashindex(PG_FUNCTION_ARGS)
 		CHECK_FOR_INTERRUPTS();
 
 		buf = ReadBufferExtended(rel, MAIN_FORKNUM, blkno, RBM_NORMAL,
-								 bstrategy);
+								 bstrategy, NULL);
 		LockBuffer(buf, BUFFER_LOCK_SHARE);
 		page = (Page) BufferGetPage(buf);
 
diff --git a/contrib/pgstattuple/pgstattuple.c b/contrib/pgstattuple/pgstattuple.c
index 0d9c2b0b65..27645938c4 100644
--- a/contrib/pgstattuple/pgstattuple.c
+++ b/contrib/pgstattuple/pgstattuple.c
@@ -376,7 +376,7 @@ pgstat_heap(Relation rel, FunctionCallInfo fcinfo)
 			CHECK_FOR_INTERRUPTS();
 
 			buffer = ReadBufferExtended(rel, MAIN_FORKNUM, block,
-										RBM_NORMAL, hscan->rs_strategy);
+										RBM_NORMAL, hscan->rs_strategy, NULL);
 			LockBuffer(buffer, BUFFER_LOCK_SHARE);
 			stat.free_space += PageGetExactFreeSpace((Page) BufferGetPage(buffer));
 			UnlockReleaseBuffer(buffer);
@@ -389,7 +389,7 @@ pgstat_heap(Relation rel, FunctionCallInfo fcinfo)
 		CHECK_FOR_INTERRUPTS();
 
 		buffer = ReadBufferExtended(rel, MAIN_FORKNUM, block,
-									RBM_NORMAL, hscan->rs_strategy);
+									RBM_NORMAL, hscan->rs_strategy, NULL);
 		LockBuffer(buffer, BUFFER_LOCK_SHARE);
 		stat.free_space += PageGetExactFreeSpace((Page) BufferGetPage(buffer));
 		UnlockReleaseBuffer(buffer);
@@ -414,7 +414,7 @@ pgstat_btree_page(pgstattuple_type *stat, Relation rel, BlockNumber blkno,
 	Buffer		buf;
 	Page		page;
 
-	buf = ReadBufferExtended(rel, MAIN_FORKNUM, blkno, RBM_NORMAL, bstrategy);
+	buf = ReadBufferExtended(rel, MAIN_FORKNUM, blkno, RBM_NORMAL, bstrategy, NULL);
 	LockBuffer(buf, BT_READ);
 	page = BufferGetPage(buf);
 
@@ -500,7 +500,7 @@ pgstat_gist_page(pgstattuple_type *stat, Relation rel, BlockNumber blkno,
 	Buffer		buf;
 	Page		page;
 
-	buf = ReadBufferExtended(rel, MAIN_FORKNUM, blkno, RBM_NORMAL, bstrategy);
+	buf = ReadBufferExtended(rel, MAIN_FORKNUM, blkno, RBM_NORMAL, bstrategy, NULL);
 	LockBuffer(buf, GIST_SHARE);
 	gistcheckpage(rel, buf);
 	page = BufferGetPage(buf);
diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index c421d89edf..500258b9d9 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -3404,6 +3404,44 @@ description | Waiting for a newly initialized WAL file to reach durable storage
       </para></entry>
      </row>
 
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>metadata_blks_read</structfield> <type>bigint</type>
+      </para>
+      <para>
+       Number of metadata (non-leaf) index disk blocks read in this database
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>metadata_blks_hit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       Number of times metadata (non-leaf) index disk blocks were found already in the buffer
+       cache
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>record_blks_read</structfield> <type>bigint</type>
+      </para>
+      <para>
+       Number of record (leaf) index disk blocks read in this database
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>record_blks_hit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       Number of times record (leaf) index disk blocks were found already in the buffer
+       cache
+      </para></entry>
+     </row>
+
      <row>
       <entry role="catalog_table_entry"><para role="column_definition">
        <structfield>blks_hit</structfield> <type>bigint</type>
@@ -4366,6 +4404,42 @@ description | Waiting for a newly initialized WAL file to reach durable storage
       </para></entry>
      </row>
 
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>idx_metadata_blks_read</structfield> <type>bigint</type>
+      </para>
+      <para>
+       Number of metadata (non-leaf) index disk blocks read from all indexes on this table
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>idx_metadata_blks_hit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       Number of metadata (non-leaf) index block hits in all indexes on this table
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>idx_record_blks_read</structfield> <type>bigint</type>
+      </para>
+      <para>
+       Number of record (leaf) index disk blocks read from all indexes on this table
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>idx_record_blks_hit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       Number of record (leaf) index block hits in all indexes on this table
+      </para></entry>
+     </row>
+
      <row>
       <entry role="catalog_table_entry"><para role="column_definition">
        <structfield>idx_blks_hit</structfield> <type>bigint</type>
@@ -4502,6 +4576,42 @@ description | Waiting for a newly initialized WAL file to reach durable storage
       </para></entry>
      </row>
 
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>idx_metadata_blks_read</structfield> <type>bigint</type>
+      </para>
+      <para>
+       Number of metadata (non-leaf) index disk blocks read from this index
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>idx_metadata_blks_hit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       Number of metadata (non-leaf) index buffer hits in this index
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>idx_record_blks_read</structfield> <type>bigint</type>
+      </para>
+      <para>
+       Number of record disk (leaf) index blocks read from this index
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>idx_record_blks_hit</structfield> <type>bigint</type>
+      </para>
+      <para>
+       Number of record (leaf) index buffer hits in this index
+      </para></entry>
+     </row>
+
      <row>
       <entry role="catalog_table_entry"><para role="column_definition">
        <structfield>idx_blks_hit</structfield> <type>bigint</type>
diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index 01e1db7f85..7cf33abfad 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1650,8 +1650,10 @@ brinGetStats(Relation index, BrinStatsData *stats)
 	Buffer		metabuffer;
 	Page		metapage;
 	BrinMetaPageData *metadata;
+	bool             hit = false;
 
-	metabuffer = ReadBuffer(index, BRIN_METAPAGE_BLKNO);
+	metabuffer = ReadBuffer(index, BRIN_METAPAGE_BLKNO, &hit);
+	pgstat_count_metadata_index_buffer(index, hit);
 	LockBuffer(metabuffer, BUFFER_LOCK_SHARE);
 	metapage = BufferGetPage(metabuffer);
 	metadata = (BrinMetaPageData *) PageGetContents(metapage);
@@ -2186,7 +2188,7 @@ brin_vacuum_scan(Relation idxrel, BufferAccessStrategy strategy)
 		CHECK_FOR_INTERRUPTS();
 
 		buf = ReadBufferExtended(idxrel, MAIN_FORKNUM, blkno,
-								 RBM_NORMAL, strategy);
+								 RBM_NORMAL, strategy, NULL);
 
 		brin_page_cleanup(idxrel, buf);
 
diff --git a/src/backend/access/brin/brin_pageops.c b/src/backend/access/brin/brin_pageops.c
index 6d8dd1512d..392bd54b3f 100644
--- a/src/backend/access/brin/brin_pageops.c
+++ b/src/backend/access/brin/brin_pageops.c
@@ -15,6 +15,7 @@
 #include "access/brin_revmap.h"
 #include "access/brin_xlog.h"
 #include "access/xloginsert.h"
+#include "pgstat.h"
 #include "miscadmin.h"
 #include "storage/bufmgr.h"
 #include "storage/freespace.h"
@@ -694,6 +695,7 @@ brin_getinsertbuffer(Relation irel, Buffer oldbuf, Size itemsz,
 	BlockNumber newblk;
 	Page		page;
 	Size		freespace;
+	bool        hit = false;
 
 	/* callers must have checked */
 	Assert(itemsz <= BrinMaxItemSize);
@@ -739,7 +741,8 @@ brin_getinsertbuffer(Relation irel, Buffer oldbuf, Size itemsz,
 				LockRelationForExtension(irel, ExclusiveLock);
 				extensionLockHeld = true;
 			}
-			buf = ReadBuffer(irel, P_NEW);
+			buf = ReadBuffer(irel, P_NEW, &hit);
+			pgstat_count_record_index_buffer(irel, hit);
 			newblk = BufferGetBlockNumber(buf);
 			*extended = true;
 
@@ -756,7 +759,8 @@ brin_getinsertbuffer(Relation irel, Buffer oldbuf, Size itemsz,
 		}
 		else
 		{
-			buf = ReadBuffer(irel, newblk);
+			buf = ReadBuffer(irel, newblk, &hit);
+			pgstat_count_record_index_buffer(irel, hit);
 		}
 
 		/*
diff --git a/src/backend/access/brin/brin_revmap.c b/src/backend/access/brin/brin_revmap.c
index 4e380ecc71..bb29738e2b 100644
--- a/src/backend/access/brin/brin_revmap.c
+++ b/src/backend/access/brin/brin_revmap.c
@@ -27,6 +27,7 @@
 #include "access/brin_xlog.h"
 #include "access/rmgr.h"
 #include "access/xloginsert.h"
+#include "pgstat.h"
 #include "miscadmin.h"
 #include "storage/bufmgr.h"
 #include "utils/rel.h"
@@ -73,8 +74,10 @@ brinRevmapInitialize(Relation idxrel, BlockNumber *pagesPerRange)
 	Buffer		meta;
 	BrinMetaPageData *metadata;
 	Page		page;
+	bool        hit = false;
 
-	meta = ReadBuffer(idxrel, BRIN_METAPAGE_BLKNO);
+	meta = ReadBuffer(idxrel, BRIN_METAPAGE_BLKNO, &hit);
+	pgstat_count_metadata_index_buffer(idxrel, hit);
 	LockBuffer(meta, BUFFER_LOCK_SHARE);
 	page = BufferGetPage(meta);
 	metadata = (BrinMetaPageData *) PageGetContents(page);
@@ -203,6 +206,7 @@ brinGetTupleForHeapBlock(BrinRevmap *revmap, BlockNumber heapBlk,
 	ItemId		lp;
 	BrinTuple  *tup;
 	ItemPointerData previptr;
+	bool            hit = false;
 
 	/* normalize the heap block number to be the first page in the range */
 	heapBlk = (heapBlk / revmap->rm_pagesPerRange) * revmap->rm_pagesPerRange;
@@ -231,7 +235,8 @@ brinGetTupleForHeapBlock(BrinRevmap *revmap, BlockNumber heapBlk,
 				ReleaseBuffer(revmap->rm_currBuf);
 
 			Assert(mapBlk != InvalidBlockNumber);
-			revmap->rm_currBuf = ReadBuffer(revmap->rm_irel, mapBlk);
+			revmap->rm_currBuf = ReadBuffer(revmap->rm_irel, mapBlk, &hit);
+			pgstat_count_metadata_index_buffer(revmap->rm_irel, hit);
 		}
 
 		LockBuffer(revmap->rm_currBuf, BUFFER_LOCK_SHARE);
@@ -269,7 +274,8 @@ brinGetTupleForHeapBlock(BrinRevmap *revmap, BlockNumber heapBlk,
 		{
 			if (BufferIsValid(*buf))
 				ReleaseBuffer(*buf);
-			*buf = ReadBuffer(idxRel, blk);
+			*buf = ReadBuffer(idxRel, blk, &hit);
+			pgstat_count_metadata_index_buffer(idxRel, hit);
 		}
 		LockBuffer(*buf, mode);
 		page = BufferGetPage(*buf);
@@ -335,6 +341,7 @@ brinRevmapDesummarizeRange(Relation idxrel, BlockNumber heapBlk)
 	OffsetNumber revmapOffset;
 	OffsetNumber regOffset;
 	ItemId		lp;
+	bool        hit = false;
 
 	revmap = brinRevmapInitialize(idxrel, &pagesPerRange);
 
@@ -363,7 +370,8 @@ brinRevmapDesummarizeRange(Relation idxrel, BlockNumber heapBlk)
 		return true;
 	}
 
-	regBuf = ReadBuffer(idxrel, ItemPointerGetBlockNumber(iptr));
+	regBuf = ReadBuffer(idxrel, ItemPointerGetBlockNumber(iptr), &hit);
+	pgstat_count_record_index_buffer(idxrel, hit);
 	LockBuffer(regBuf, BUFFER_LOCK_EXCLUSIVE);
 	regPg = BufferGetPage(regBuf);
 
@@ -463,6 +471,7 @@ static Buffer
 revmap_get_buffer(BrinRevmap *revmap, BlockNumber heapBlk)
 {
 	BlockNumber mapBlk;
+	bool        hit = false;
 
 	/* Translate the heap block number to physical index location. */
 	mapBlk = revmap_get_blkno(revmap, heapBlk);
@@ -485,7 +494,8 @@ revmap_get_buffer(BrinRevmap *revmap, BlockNumber heapBlk)
 		if (revmap->rm_currBuf != InvalidBuffer)
 			ReleaseBuffer(revmap->rm_currBuf);
 
-		revmap->rm_currBuf = ReadBuffer(revmap->rm_irel, mapBlk);
+		revmap->rm_currBuf = ReadBuffer(revmap->rm_irel, mapBlk, &hit);
+		pgstat_count_metadata_index_buffer(revmap->rm_irel, hit);
 	}
 
 	return revmap->rm_currBuf;
@@ -528,6 +538,7 @@ revmap_physical_extend(BrinRevmap *revmap)
 	BlockNumber mapBlk;
 	BlockNumber nblocks;
 	Relation	irel = revmap->rm_irel;
+	bool        hit = false;
 
 	/*
 	 * Lock the metapage. This locks out concurrent extensions of the revmap,
@@ -553,7 +564,8 @@ revmap_physical_extend(BrinRevmap *revmap)
 	nblocks = RelationGetNumberOfBlocks(irel);
 	if (mapBlk < nblocks)
 	{
-		buf = ReadBuffer(irel, mapBlk);
+		buf = ReadBuffer(irel, mapBlk, &hit);
+		pgstat_count_metadata_index_buffer(irel, hit);
 		LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
 		page = BufferGetPage(buf);
 	}
diff --git a/src/backend/access/gin/ginbtree.c b/src/backend/access/gin/ginbtree.c
index 26a0bdc206..b35657ee8e 100644
--- a/src/backend/access/gin/ginbtree.c
+++ b/src/backend/access/gin/ginbtree.c
@@ -18,6 +18,7 @@
 #include "access/ginxlog.h"
 #include "access/xloginsert.h"
 #include "miscadmin.h"
+#include "pgstat.h"
 #include "storage/predicate.h"
 #include "utils/injection_point.h"
 #include "utils/memutils.h"
@@ -84,10 +85,12 @@ ginFindLeafPage(GinBtree btree, bool searchMode,
 				bool rootConflictCheck)
 {
 	GinBtreeStack *stack;
+	bool          hit = false;
 
 	stack = (GinBtreeStack *) palloc(sizeof(GinBtreeStack));
 	stack->blkno = btree->rootBlkno;
-	stack->buffer = ReadBuffer(btree->index, btree->rootBlkno);
+	stack->buffer = ReadBuffer(btree->index, btree->rootBlkno, &hit);
+	pgstat_count_metadata_index_buffer(btree->index, hit);
 	stack->parent = NULL;
 	stack->predictNumber = 1;
 
@@ -148,7 +151,9 @@ ginFindLeafPage(GinBtree btree, bool searchMode,
 		{
 			/* in search mode we may forget path to leaf */
 			stack->blkno = child;
-			stack->buffer = ReleaseAndReadBuffer(stack->buffer, btree->index, stack->blkno);
+			stack->buffer = ReleaseAndReadBuffer(stack->buffer, btree->index, stack->blkno,
+												 &hit);
+			pgstat_count_index_buffer(btree->index, GinPageIsLeaf(BufferGetPage(stack->buffer)), hit);
 		}
 		else
 		{
@@ -157,7 +162,8 @@ ginFindLeafPage(GinBtree btree, bool searchMode,
 			ptr->parent = stack;
 			stack = ptr;
 			stack->blkno = child;
-			stack->buffer = ReadBuffer(btree->index, stack->blkno);
+			stack->buffer = ReadBuffer(btree->index, stack->blkno, &hit);
+			pgstat_count_index_buffer(btree->index, GinPageIsLeaf(BufferGetPage(stack->buffer)), hit);
 			stack->predictNumber = 1;
 		}
 	}
@@ -177,12 +183,14 @@ Buffer
 ginStepRight(Buffer buffer, Relation index, int lockmode)
 {
 	Buffer		nextbuffer;
+	bool        hit = false;
 	Page		page = BufferGetPage(buffer);
 	bool		isLeaf = GinPageIsLeaf(page);
 	bool		isData = GinPageIsData(page);
 	BlockNumber blkno = GinPageGetOpaque(page)->rightlink;
 
-	nextbuffer = ReadBuffer(index, blkno);
+	nextbuffer = ReadBuffer(index, blkno, &hit);
+	pgstat_count_index_buffer(index, isLeaf, hit);
 	LockBuffer(nextbuffer, lockmode);
 	UnlockReleaseBuffer(buffer);
 
@@ -224,6 +232,7 @@ ginFindParents(GinBtree btree, GinBtreeStack *stack)
 	OffsetNumber offset;
 	GinBtreeStack *root;
 	GinBtreeStack *ptr;
+	bool          hit = false;
 
 	/*
 	 * Unwind the stack all the way up to the root, leaving only the root
@@ -314,7 +323,8 @@ ginFindParents(GinBtree btree, GinBtreeStack *stack)
 
 		/* Descend down to next level */
 		blkno = leftmostBlkno;
-		buffer = ReadBuffer(btree->index, blkno);
+		buffer = ReadBuffer(btree->index, blkno, &hit);
+		pgstat_count_index_buffer(btree->index, GinPageIsLeaf(BufferGetPage(buffer)), hit);
 	}
 }
 
diff --git a/src/backend/access/gin/ginfast.c b/src/backend/access/gin/ginfast.c
index a6d88572cc..bd10910222 100644
--- a/src/backend/access/gin/ginfast.c
+++ b/src/backend/access/gin/ginfast.c
@@ -25,6 +25,7 @@
 #include "catalog/pg_am.h"
 #include "commands/vacuum.h"
 #include "miscadmin.h"
+#include "pgstat.h"
 #include "port/pg_bitutils.h"
 #include "postmaster/autovacuum.h"
 #include "storage/indexfsm.h"
@@ -229,6 +230,7 @@ ginHeapTupleFastInsert(GinState *ginstate, GinTupleCollector *collector)
 	bool		needCleanup = false;
 	int			cleanupSize;
 	bool		needWal;
+	bool        hit = false;
 
 	if (collector->ntuples == 0)
 		return;
@@ -239,7 +241,8 @@ ginHeapTupleFastInsert(GinState *ginstate, GinTupleCollector *collector)
 	data.ntuples = 0;
 	data.newRightlink = data.prevTail = InvalidBlockNumber;
 
-	metabuffer = ReadBuffer(index, GIN_METAPAGE_BLKNO);
+	metabuffer = ReadBuffer(index, GIN_METAPAGE_BLKNO, &hit);
+	pgstat_count_metadata_index_buffer(index, hit);
 	metapage = BufferGetPage(metabuffer);
 
 	/*
@@ -319,7 +322,8 @@ ginHeapTupleFastInsert(GinState *ginstate, GinTupleCollector *collector)
 			data.prevTail = metadata->tail;
 			data.newRightlink = sublist.head;
 
-			buffer = ReadBuffer(index, metadata->tail);
+			buffer = ReadBuffer(index, metadata->tail, &hit);
+			pgstat_count_metadata_index_buffer(index, hit);
 			LockBuffer(buffer, GIN_EXCLUSIVE);
 			page = BufferGetPage(buffer);
 
@@ -358,7 +362,8 @@ ginHeapTupleFastInsert(GinState *ginstate, GinTupleCollector *collector)
 
 		CheckForSerializableConflictIn(index, NULL, GIN_METAPAGE_BLKNO);
 
-		buffer = ReadBuffer(index, metadata->tail);
+		buffer = ReadBuffer(index, metadata->tail, &hit);
+		pgstat_count_metadata_index_buffer(index, hit);
 		LockBuffer(buffer, GIN_EXCLUSIVE);
 		page = BufferGetPage(buffer);
 
@@ -557,6 +562,7 @@ shiftList(Relation index, Buffer metabuffer, BlockNumber newHead,
 	Page		metapage;
 	GinMetaPageData *metadata;
 	BlockNumber blknoToDelete;
+	bool        hit = false;
 
 	metapage = BufferGetPage(metabuffer);
 	metadata = GinPageGetMeta(metapage);
@@ -575,7 +581,8 @@ shiftList(Relation index, Buffer metabuffer, BlockNumber newHead,
 		while (data.ndeleted < GIN_NDELETE_AT_ONCE && blknoToDelete != newHead)
 		{
 			freespace[data.ndeleted] = blknoToDelete;
-			buffers[data.ndeleted] = ReadBuffer(index, blknoToDelete);
+			buffers[data.ndeleted] = ReadBuffer(index, blknoToDelete, &hit);
+			pgstat_count_metadata_index_buffer(index, hit);
 			LockBuffer(buffers[data.ndeleted], GIN_EXCLUSIVE);
 			page = BufferGetPage(buffers[data.ndeleted]);
 
@@ -796,6 +803,7 @@ ginInsertCleanup(GinState *ginstate, bool full_clean,
 	bool		cleanupFinish = false;
 	bool		fsm_vac = false;
 	int			workMemory;
+	bool        hit = false;
 
 	/*
 	 * We would like to prevent concurrent cleanup process. For that we will
@@ -827,7 +835,8 @@ ginInsertCleanup(GinState *ginstate, bool full_clean,
 		workMemory = work_mem;
 	}
 
-	metabuffer = ReadBuffer(index, GIN_METAPAGE_BLKNO);
+	metabuffer = ReadBuffer(index, GIN_METAPAGE_BLKNO, &hit);
+	pgstat_count_metadata_index_buffer(index, hit);
 	LockBuffer(metabuffer, GIN_SHARE);
 	metapage = BufferGetPage(metabuffer);
 	metadata = GinPageGetMeta(metapage);
@@ -850,7 +859,8 @@ ginInsertCleanup(GinState *ginstate, bool full_clean,
 	 * Read and lock head of pending list
 	 */
 	blkno = metadata->head;
-	buffer = ReadBuffer(index, blkno);
+	buffer = ReadBuffer(index, blkno, &hit);
+	pgstat_count_record_index_buffer(index, hit);
 	LockBuffer(buffer, GIN_SHARE);
 	page = BufferGetPage(buffer);
 
@@ -1003,7 +1013,8 @@ ginInsertCleanup(GinState *ginstate, bool full_clean,
 		 * Read next page in pending list
 		 */
 		vacuum_delay_point(false);
-		buffer = ReadBuffer(index, blkno);
+		buffer = ReadBuffer(index, blkno, &hit);
+		pgstat_count_record_index_buffer(index, hit);
 		LockBuffer(buffer, GIN_SHARE);
 		page = BufferGetPage(buffer);
 	}
diff --git a/src/backend/access/gin/ginget.c b/src/backend/access/gin/ginget.c
index f29ccd3c2d..fce1162e6a 100644
--- a/src/backend/access/gin/ginget.c
+++ b/src/backend/access/gin/ginget.c
@@ -18,6 +18,7 @@
 #include "access/relscan.h"
 #include "common/pg_prng.h"
 #include "miscadmin.h"
+#include "pgstat.h"
 #include "storage/predicate.h"
 #include "utils/datum.h"
 #include "utils/memutils.h"
@@ -1467,6 +1468,7 @@ scanGetCandidate(IndexScanDesc scan, pendingPosition *pos)
 	OffsetNumber maxoff;
 	Page		page;
 	IndexTuple	itup;
+	bool        hit = false;
 
 	ItemPointerSetInvalid(&pos->item);
 	for (;;)
@@ -1493,7 +1495,8 @@ scanGetCandidate(IndexScanDesc scan, pendingPosition *pos)
 				 * current page.  So, we lock next page before releasing the
 				 * current one
 				 */
-				Buffer		tmpbuf = ReadBuffer(scan->indexRelation, blkno);
+				Buffer		tmpbuf = ReadBuffer(scan->indexRelation, blkno, &hit);
+				pgstat_count_record_index_buffer(scan->indexRelation, hit);
 
 				LockBuffer(tmpbuf, GIN_SHARE);
 				UnlockReleaseBuffer(pos->pendingBuffer);
@@ -1840,10 +1843,12 @@ scanPendingInsert(IndexScanDesc scan, TIDBitmap *tbm, int64 *ntids)
 				match;
 	int			i;
 	pendingPosition pos;
-	Buffer		metabuffer = ReadBuffer(scan->indexRelation, GIN_METAPAGE_BLKNO);
+	bool            hit = false;
+	Buffer		metabuffer = ReadBuffer(scan->indexRelation, GIN_METAPAGE_BLKNO, &hit);
 	Page		page;
 	BlockNumber blkno;
 
+	pgstat_count_metadata_index_buffer(scan->indexRelation, hit);
 	*ntids = 0;
 
 	/*
@@ -1867,7 +1872,8 @@ scanPendingInsert(IndexScanDesc scan, TIDBitmap *tbm, int64 *ntids)
 		return;
 	}
 
-	pos.pendingBuffer = ReadBuffer(scan->indexRelation, blkno);
+	pos.pendingBuffer = ReadBuffer(scan->indexRelation, blkno, &hit);
+	pgstat_count_record_index_buffer(scan->indexRelation, hit);
 	LockBuffer(pos.pendingBuffer, GIN_SHARE);
 	pos.firstOffset = FirstOffsetNumber;
 	UnlockReleaseBuffer(metabuffer);
diff --git a/src/backend/access/gin/ginutil.c b/src/backend/access/gin/ginutil.c
index 78f7b7a249..0d42f4ca87 100644
--- a/src/backend/access/gin/ginutil.c
+++ b/src/backend/access/gin/ginutil.c
@@ -22,6 +22,7 @@
 #include "catalog/pg_type.h"
 #include "commands/progress.h"
 #include "commands/vacuum.h"
+#include "pgstat.h"
 #include "miscadmin.h"
 #include "storage/indexfsm.h"
 #include "utils/builtins.h"
@@ -305,6 +306,7 @@ Buffer
 GinNewBuffer(Relation index)
 {
 	Buffer		buffer;
+	bool        hit = false;
 
 	/* First, try to get a page from FSM */
 	for (;;)
@@ -314,7 +316,8 @@ GinNewBuffer(Relation index)
 		if (blkno == InvalidBlockNumber)
 			break;
 
-		buffer = ReadBuffer(index, blkno);
+		buffer = ReadBuffer(index, blkno, &hit);
+		pgstat_count_index_buffer(index, GinPageIsLeaf(BufferGetPage(buffer)), hit);
 
 		/*
 		 * We have to guard against the possibility that someone else already
@@ -630,8 +633,10 @@ ginGetStats(Relation index, GinStatsData *stats)
 	Buffer		metabuffer;
 	Page		metapage;
 	GinMetaPageData *metadata;
+	bool            hit = false;
 
-	metabuffer = ReadBuffer(index, GIN_METAPAGE_BLKNO);
+	metabuffer = ReadBuffer(index, GIN_METAPAGE_BLKNO, &hit);
+	pgstat_count_metadata_index_buffer(index, hit);
 	LockBuffer(metabuffer, GIN_SHARE);
 	metapage = BufferGetPage(metabuffer);
 	metadata = GinPageGetMeta(metapage);
@@ -657,8 +662,10 @@ ginUpdateStats(Relation index, const GinStatsData *stats, bool is_build)
 	Buffer		metabuffer;
 	Page		metapage;
 	GinMetaPageData *metadata;
+	bool            hit = false;
 
-	metabuffer = ReadBuffer(index, GIN_METAPAGE_BLKNO);
+	metabuffer = ReadBuffer(index, GIN_METAPAGE_BLKNO, &hit);
+	pgstat_count_metadata_index_buffer(index, hit);
 	LockBuffer(metabuffer, GIN_EXCLUSIVE);
 	metapage = BufferGetPage(metabuffer);
 	metadata = GinPageGetMeta(metapage);
diff --git a/src/backend/access/gin/ginvacuum.c b/src/backend/access/gin/ginvacuum.c
index fbbe3a6dd7..250bffa49a 100644
--- a/src/backend/access/gin/ginvacuum.c
+++ b/src/backend/access/gin/ginvacuum.c
@@ -143,11 +143,11 @@ ginDeletePage(GinVacuumState *gvs, BlockNumber deleteBlkno, BlockNumber leftBlkn
 	 * deletable, parent and left pages.
 	 */
 	lBuffer = ReadBufferExtended(gvs->index, MAIN_FORKNUM, leftBlkno,
-								 RBM_NORMAL, gvs->strategy);
+								 RBM_NORMAL, gvs->strategy, NULL);
 	dBuffer = ReadBufferExtended(gvs->index, MAIN_FORKNUM, deleteBlkno,
-								 RBM_NORMAL, gvs->strategy);
+								 RBM_NORMAL, gvs->strategy, NULL);
 	pBuffer = ReadBufferExtended(gvs->index, MAIN_FORKNUM, parentBlkno,
-								 RBM_NORMAL, gvs->strategy);
+								 RBM_NORMAL, gvs->strategy, NULL);
 
 	page = BufferGetPage(dBuffer);
 	rightlink = GinPageGetOpaque(page)->rightlink;
@@ -270,7 +270,7 @@ ginScanToDelete(GinVacuumState *gvs, BlockNumber blkno, bool isRoot,
 	}
 
 	buffer = ReadBufferExtended(gvs->index, MAIN_FORKNUM, blkno,
-								RBM_NORMAL, gvs->strategy);
+								RBM_NORMAL, gvs->strategy, NULL);
 
 	if (!isRoot)
 		LockBuffer(buffer, GIN_EXCLUSIVE);
@@ -355,7 +355,7 @@ ginVacuumPostingTreeLeaves(GinVacuumState *gvs, BlockNumber blkno)
 		PostingItem *pitem;
 
 		buffer = ReadBufferExtended(gvs->index, MAIN_FORKNUM, blkno,
-									RBM_NORMAL, gvs->strategy);
+									RBM_NORMAL, gvs->strategy, NULL);
 		LockBuffer(buffer, GIN_SHARE);
 		page = BufferGetPage(buffer);
 
@@ -396,7 +396,7 @@ ginVacuumPostingTreeLeaves(GinVacuumState *gvs, BlockNumber blkno)
 			break;
 
 		buffer = ReadBufferExtended(gvs->index, MAIN_FORKNUM, blkno,
-									RBM_NORMAL, gvs->strategy);
+									RBM_NORMAL, gvs->strategy, NULL);
 		LockBuffer(buffer, GIN_EXCLUSIVE);
 		page = BufferGetPage(buffer);
 	}
@@ -419,7 +419,7 @@ ginVacuumPostingTree(GinVacuumState *gvs, BlockNumber rootBlkno)
 				   *tmp;
 
 		buffer = ReadBufferExtended(gvs->index, MAIN_FORKNUM, rootBlkno,
-									RBM_NORMAL, gvs->strategy);
+									RBM_NORMAL, gvs->strategy, NULL);
 
 		/*
 		 * Lock posting tree root for cleanup to ensure there are no
@@ -598,7 +598,7 @@ ginbulkdelete(IndexVacuumInfo *info, IndexBulkDeleteResult *stats,
 	gvs.result = stats;
 
 	buffer = ReadBufferExtended(index, MAIN_FORKNUM, blkno,
-								RBM_NORMAL, info->strategy);
+								RBM_NORMAL, info->strategy, NULL);
 
 	/* find leaf page */
 	for (;;)
@@ -631,7 +631,7 @@ ginbulkdelete(IndexVacuumInfo *info, IndexBulkDeleteResult *stats,
 
 		UnlockReleaseBuffer(buffer);
 		buffer = ReadBufferExtended(index, MAIN_FORKNUM, blkno,
-									RBM_NORMAL, info->strategy);
+									RBM_NORMAL, info->strategy, NULL);
 	}
 
 	/* right now we found leftmost page in entry's BTree */
@@ -674,7 +674,7 @@ ginbulkdelete(IndexVacuumInfo *info, IndexBulkDeleteResult *stats,
 			break;
 
 		buffer = ReadBufferExtended(index, MAIN_FORKNUM, blkno,
-									RBM_NORMAL, info->strategy);
+									RBM_NORMAL, info->strategy, NULL);
 		LockBuffer(buffer, GIN_EXCLUSIVE);
 	}
 
@@ -751,7 +751,7 @@ ginvacuumcleanup(IndexVacuumInfo *info, IndexBulkDeleteResult *stats)
 		vacuum_delay_point(false);
 
 		buffer = ReadBufferExtended(index, MAIN_FORKNUM, blkno,
-									RBM_NORMAL, info->strategy);
+									RBM_NORMAL, info->strategy, NULL);
 		LockBuffer(buffer, GIN_SHARE);
 		page = (Page) BufferGetPage(buffer);
 
diff --git a/src/backend/access/gist/gist.c b/src/backend/access/gist/gist.c
index 7b24380c97..77df96a280 100644
--- a/src/backend/access/gist/gist.c
+++ b/src/backend/access/gist/gist.c
@@ -20,6 +20,7 @@
 #include "catalog/pg_collation.h"
 #include "commands/vacuum.h"
 #include "miscadmin.h"
+#include "pgstat.h"
 #include "nodes/execnodes.h"
 #include "storage/predicate.h"
 #include "utils/fmgrprotos.h"
@@ -645,6 +646,7 @@ gistdoinsert(Relation r, IndexTuple itup, Size freespace,
 	GISTInsertStack *stack;
 	GISTInsertState state;
 	bool		xlocked = false;
+	bool        hit = false;
 
 	memset(&state, 0, sizeof(GISTInsertState));
 	state.freespace = freespace;
@@ -683,8 +685,10 @@ gistdoinsert(Relation r, IndexTuple itup, Size freespace,
 			state.stack = stack = stack->parent;
 		}
 
-		if (XLogRecPtrIsInvalid(stack->lsn))
-			stack->buffer = ReadBuffer(state.r, stack->blkno);
+		if (XLogRecPtrIsInvalid(stack->lsn)) {
+			stack->buffer = ReadBuffer(state.r, stack->blkno, &hit);
+			pgstat_count_index_buffer(state.r, !GistPageIsLeaf(BufferGetPage(stack->buffer)), hit);
+		}
 
 		/*
 		 * Be optimistic and grab shared lock first. Swap it for an exclusive
@@ -923,6 +927,7 @@ gistFindPath(Relation r, BlockNumber child, OffsetNumber *downlinkoffnum)
 	GISTInsertStack *top,
 			   *ptr;
 	BlockNumber blkno;
+	bool        hit = false;
 
 	top = (GISTInsertStack *) palloc0(sizeof(GISTInsertStack));
 	top->blkno = GIST_ROOT_BLKNO;
@@ -935,10 +940,11 @@ gistFindPath(Relation r, BlockNumber child, OffsetNumber *downlinkoffnum)
 		top = linitial(fifo);
 		fifo = list_delete_first(fifo);
 
-		buffer = ReadBuffer(r, top->blkno);
+		buffer = ReadBuffer(r, top->blkno, &hit);
 		LockBuffer(buffer, GIST_SHARE);
 		gistcheckpage(r, buffer);
 		page = (Page) BufferGetPage(buffer);
+		pgstat_count_index_buffer(r, !GistPageIsLeaf(page), hit);
 
 		if (GistPageIsLeaf(page))
 		{
@@ -1031,6 +1037,7 @@ gistFindCorrectParent(Relation r, GISTInsertStack *child, bool is_build)
 	IndexTuple	idxtuple;
 	OffsetNumber maxoff;
 	GISTInsertStack *ptr;
+	bool            hit = false;
 
 	gistcheckpage(r, parent->buffer);
 	parent->page = (Page) BufferGetPage(parent->buffer);
@@ -1095,10 +1102,11 @@ gistFindCorrectParent(Relation r, GISTInsertStack *child, bool is_build)
 			 */
 			break;
 		}
-		parent->buffer = ReadBuffer(r, parent->blkno);
+		parent->buffer = ReadBuffer(r, parent->blkno, &hit);
 		LockBuffer(parent->buffer, GIST_EXCLUSIVE);
 		gistcheckpage(r, parent->buffer);
 		parent->page = (Page) BufferGetPage(parent->buffer);
+		pgstat_count_index_buffer(r, !GistPageIsLeaf(parent->page), hit);
 	}
 
 	/*
@@ -1120,8 +1128,9 @@ gistFindCorrectParent(Relation r, GISTInsertStack *child, bool is_build)
 	/* note we don't lock them or gistcheckpage them here! */
 	while (ptr)
 	{
-		ptr->buffer = ReadBuffer(r, ptr->blkno);
+		ptr->buffer = ReadBuffer(r, ptr->blkno, &hit);
 		ptr->page = (Page) BufferGetPage(ptr->buffer);
+		pgstat_count_index_buffer(r, !GistPageIsLeaf(ptr->page), hit);
 		ptr = ptr->parent;
 	}
 
@@ -1203,6 +1212,7 @@ gistfixsplit(GISTInsertState *state, GISTSTATE *giststate)
 	Buffer		buf;
 	Page		page;
 	List	   *splitinfo = NIL;
+	bool       hit = false;
 
 	ereport(LOG,
 			(errmsg("fixing incomplete split in index \"%s\", block %u",
@@ -1235,7 +1245,8 @@ gistfixsplit(GISTInsertState *state, GISTSTATE *giststate)
 		if (GistFollowRight(page))
 		{
 			/* lock next page */
-			buf = ReadBuffer(state->r, GistPageGetOpaque(page)->rightlink);
+			buf = ReadBuffer(state->r, GistPageGetOpaque(page)->rightlink, &hit);
+			pgstat_count_index_buffer(state->r, !GistPageIsLeaf(BufferGetPage(buf)), hit);
 			LockBuffer(buf, GIST_EXCLUSIVE);
 		}
 		else
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9e707167d9..8c7fc6e7d5 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -41,6 +41,7 @@
 #include "miscadmin.h"
 #include "nodes/execnodes.h"
 #include "optimizer/optimizer.h"
+#include "pgstat.h"
 #include "storage/bufmgr.h"
 #include "storage/bulk_write.h"
 
@@ -935,6 +936,7 @@ gistProcessItup(GISTBuildState *buildstate, IndexTuple itup,
 	int			level;
 	OffsetNumber downlinkoffnum = InvalidOffsetNumber;
 	BlockNumber parentblkno = InvalidBlockNumber;
+	bool        hit = false;
 
 	CHECK_FOR_INTERRUPTS();
 
@@ -966,10 +968,12 @@ gistProcessItup(GISTBuildState *buildstate, IndexTuple itup,
 		 * descend down to.
 		 */
 
-		buffer = ReadBuffer(indexrel, blkno);
+		buffer = ReadBuffer(indexrel, blkno, &hit);
 		LockBuffer(buffer, GIST_EXCLUSIVE);
 
 		page = (Page) BufferGetPage(buffer);
+		pgstat_count_index_buffer(indexrel, !GistPageIsLeaf(page), hit);
+
 		childoffnum = gistchoose(indexrel, page, itup, giststate);
 		iid = PageGetItemId(page, childoffnum);
 		idxtuple = (IndexTuple) PageGetItem(page, iid);
@@ -1029,7 +1033,8 @@ gistProcessItup(GISTBuildState *buildstate, IndexTuple itup,
 		 * We've reached a leaf page. Place the tuple here.
 		 */
 		Assert(level == 0);
-		buffer = ReadBuffer(indexrel, blkno);
+		buffer = ReadBuffer(indexrel, blkno, &hit);
+		pgstat_count_record_index_buffer(indexrel, hit);
 		LockBuffer(buffer, GIST_EXCLUSIVE);
 		gistbufferinginserttuples(buildstate, buffer, level,
 								  &itup, 1, InvalidOffsetNumber,
@@ -1061,6 +1066,7 @@ gistbufferinginserttuples(GISTBuildState *buildstate, Buffer buffer, int level,
 	List	   *splitinfo;
 	bool		is_split;
 	BlockNumber placed_to_blk = InvalidBlockNumber;
+	bool        hit = false;
 
 	is_split = gistplacetopage(buildstate->indexrel,
 							   buildstate->freespace,
@@ -1102,7 +1108,8 @@ gistbufferinginserttuples(GISTBuildState *buildstate, Buffer buffer, int level,
 				ItemId		iid = PageGetItemId(page, off);
 				IndexTuple	idxtuple = (IndexTuple) PageGetItem(page, iid);
 				BlockNumber childblkno = ItemPointerGetBlockNumber(&(idxtuple->t_tid));
-				Buffer		childbuf = ReadBuffer(buildstate->indexrel, childblkno);
+				Buffer		childbuf = ReadBuffer(buildstate->indexrel, childblkno, &hit);
+				pgstat_count_record_index_buffer(buildstate->indexrel, hit);
 
 				LockBuffer(childbuf, GIST_SHARE);
 				gistMemorizeAllDownlinks(buildstate, childbuf);
@@ -1232,6 +1239,7 @@ gistBufferingFindCorrectParent(GISTBuildState *buildstate,
 	Page		page;
 	OffsetNumber maxoff;
 	OffsetNumber off;
+	bool         hit = false;
 
 	if (level > 0)
 		parent = gistGetParent(buildstate, childblkno);
@@ -1246,8 +1254,9 @@ gistBufferingFindCorrectParent(GISTBuildState *buildstate,
 		parent = *parentblkno;
 	}
 
-	buffer = ReadBuffer(buildstate->indexrel, parent);
+	buffer = ReadBuffer(buildstate->indexrel, parent, &hit);
 	page = BufferGetPage(buffer);
+	pgstat_count_index_buffer(buildstate->indexrel, !GistPageIsLeaf(page), hit);
 	LockBuffer(buffer, GIST_EXCLUSIVE);
 	gistcheckpage(buildstate->indexrel, buffer);
 	maxoff = PageGetMaxOffsetNumber(page);
@@ -1440,8 +1449,9 @@ gistGetMaxLevel(Relation index)
 		Buffer		buffer;
 		Page		page;
 		IndexTuple	itup;
+		bool        hit = false;
 
-		buffer = ReadBuffer(index, blkno);
+		buffer = ReadBuffer(index, blkno, &hit);
 
 		/*
 		 * There's no concurrent access during index build, so locking is just
@@ -1454,9 +1464,12 @@ gistGetMaxLevel(Relation index)
 		{
 			/* We hit the bottom, so we're done. */
 			UnlockReleaseBuffer(buffer);
+			pgstat_count_record_index_buffer(index, hit);
 			break;
 		}
 
+		pgstat_count_metadata_index_buffer(index, hit);
+
 		/*
 		 * Pick the first downlink on the page, and follow it. It doesn't
 		 * matter which downlink we choose, the tree has the same depth
diff --git a/src/backend/access/gist/gistget.c b/src/backend/access/gist/gistget.c
index 387d997234..5f6803e621 100644
--- a/src/backend/access/gist/gistget.c
+++ b/src/backend/access/gist/gistget.c
@@ -44,18 +44,20 @@ gistkillitems(IndexScanDesc scan)
 	ItemId		iid;
 	int			i;
 	bool		killedsomething = false;
+	bool 		hit = false;
 
 	Assert(so->curBlkno != InvalidBlockNumber);
 	Assert(!XLogRecPtrIsInvalid(so->curPageLSN));
 	Assert(so->killedItems != NULL);
 
-	buffer = ReadBuffer(scan->indexRelation, so->curBlkno);
+	buffer = ReadBuffer(scan->indexRelation, so->curBlkno, &hit);
 	if (!BufferIsValid(buffer))
 		return;
 
 	LockBuffer(buffer, GIST_SHARE);
 	gistcheckpage(scan->indexRelation, buffer);
 	page = BufferGetPage(buffer);
+	pgstat_count_index_buffer(scan->indexRelation, !GistPageIsLeaf(page), hit);
 
 	/*
 	 * If page LSN differs it means that the page was modified since the last
@@ -337,14 +339,16 @@ gistScanPage(IndexScanDesc scan, GISTSearchItem *pageItem,
 	OffsetNumber maxoff;
 	OffsetNumber i;
 	MemoryContext oldcxt;
+	bool          hit = false;
 
 	Assert(!GISTSearchItemIsHeap(*pageItem));
 
-	buffer = ReadBuffer(scan->indexRelation, pageItem->blkno);
+	buffer = ReadBuffer(scan->indexRelation, pageItem->blkno, &hit);
 	LockBuffer(buffer, GIST_SHARE);
 	PredicateLockPage(r, BufferGetBlockNumber(buffer), scan->xs_snapshot);
 	gistcheckpage(scan->indexRelation, buffer);
 	page = BufferGetPage(buffer);
+	pgstat_count_index_buffer(scan->indexRelation, !GistPageIsLeaf(page), hit);
 	opaque = GistPageGetOpaque(page);
 
 	/*
diff --git a/src/backend/access/gist/gistutil.c b/src/backend/access/gist/gistutil.c
index a6b701943d..5bbc29a0ee 100644
--- a/src/backend/access/gist/gistutil.c
+++ b/src/backend/access/gist/gistutil.c
@@ -19,6 +19,7 @@
 #include "access/htup_details.h"
 #include "access/reloptions.h"
 #include "common/pg_prng.h"
+#include "pgstat.h"
 #include "storage/indexfsm.h"
 #include "utils/float.h"
 #include "utils/fmgrprotos.h"
@@ -824,6 +825,7 @@ Buffer
 gistNewBuffer(Relation r, Relation heaprel)
 {
 	Buffer		buffer;
+	bool        hit = false;
 
 	/* First, try to get a page from FSM */
 	for (;;)
@@ -833,7 +835,7 @@ gistNewBuffer(Relation r, Relation heaprel)
 		if (blkno == InvalidBlockNumber)
 			break;				/* nothing left in FSM */
 
-		buffer = ReadBuffer(r, blkno);
+		buffer = ReadBuffer(r, blkno, &hit);
 
 		/*
 		 * We have to guard against the possibility that someone else already
@@ -842,6 +844,7 @@ gistNewBuffer(Relation r, Relation heaprel)
 		if (ConditionalLockBuffer(buffer))
 		{
 			Page		page = BufferGetPage(buffer);
+			pgstat_count_index_buffer(r, !GistPageIsLeaf(page), hit);
 
 			/*
 			 * If the page was never initialized, it's OK to use.
diff --git a/src/backend/access/gist/gistvacuum.c b/src/backend/access/gist/gistvacuum.c
index 6a359c98c6..9cfcbcc3f3 100644
--- a/src/backend/access/gist/gistvacuum.c
+++ b/src/backend/access/gist/gistvacuum.c
@@ -491,7 +491,7 @@ restart:
 		vacuum_delay_point(false);
 
 		buffer = ReadBufferExtended(rel, MAIN_FORKNUM, blkno, RBM_NORMAL,
-									info->strategy);
+									info->strategy, NULL);
 		goto restart;
 	}
 }
@@ -524,7 +524,7 @@ gistvacuum_delete_empty_pages(IndexVacuumInfo *info, GistVacState *vstate)
 		int			deleted;
 
 		buffer = ReadBufferExtended(rel, MAIN_FORKNUM, (BlockNumber) blkno,
-									RBM_NORMAL, info->strategy);
+									RBM_NORMAL, info->strategy, NULL);
 
 		LockBuffer(buffer, GIST_SHARE);
 		page = (Page) BufferGetPage(buffer);
@@ -590,7 +590,7 @@ gistvacuum_delete_empty_pages(IndexVacuumInfo *info, GistVacState *vstate)
 				break;
 
 			leafbuf = ReadBufferExtended(rel, MAIN_FORKNUM, leafs_to_delete[i],
-										 RBM_NORMAL, info->strategy);
+										 RBM_NORMAL, info->strategy, NULL);
 			LockBuffer(leafbuf, GIST_EXCLUSIVE);
 			gistcheckpage(rel, leafbuf);
 
diff --git a/src/backend/access/hash/hash.c b/src/backend/access/hash/hash.c
index 53061c819f..d991b5d5f7 100644
--- a/src/backend/access/hash/hash.c
+++ b/src/backend/access/hash/hash.c
@@ -505,6 +505,7 @@ loop_top:
 		HashPageOpaque bucket_opaque;
 		Page		page;
 		bool		split_cleanup = false;
+		bool        hit = false;
 
 		/* Get address of bucket's start page */
 		bucket_blkno = BUCKET_TO_BLKNO(cachedmetap, cur_bucket);
@@ -515,7 +516,9 @@ loop_top:
 		 * We need to acquire a cleanup lock on the primary bucket page to out
 		 * wait concurrent scans before deleting the dead tuples.
 		 */
-		buf = ReadBufferExtended(rel, MAIN_FORKNUM, blkno, RBM_NORMAL, info->strategy);
+		buf = ReadBufferExtended(rel, MAIN_FORKNUM, blkno, RBM_NORMAL, info->strategy,
+								 &hit);
+		pgstat_count_record_index_buffer(rel, hit);
 		LockBufferForCleanup(buf);
 		_hash_checkpage(rel, buf, LH_BUCKET_PAGE);
 
diff --git a/src/backend/access/hash/hashpage.c b/src/backend/access/hash/hashpage.c
index b8e5bd005e..173b406e0c 100644
--- a/src/backend/access/hash/hashpage.c
+++ b/src/backend/access/hash/hashpage.c
@@ -32,6 +32,7 @@
 #include "access/hash_xlog.h"
 #include "access/xloginsert.h"
 #include "miscadmin.h"
+#include "pgstat.h"
 #include "port/pg_bitutils.h"
 #include "storage/predicate.h"
 #include "storage/smgr.h"
@@ -70,11 +71,13 @@ Buffer
 _hash_getbuf(Relation rel, BlockNumber blkno, int access, int flags)
 {
 	Buffer		buf;
+	bool        hit = false;
 
 	if (blkno == P_NEW)
 		elog(ERROR, "hash AM does not use P_NEW");
 
-	buf = ReadBuffer(rel, blkno);
+	buf = ReadBuffer(rel, blkno, &hit);
+	pgstat_count_index_buffer(rel, flags == LH_META_PAGE, hit);
 
 	if (access != HASH_NOLOCK)
 		LockBuffer(buf, access);
@@ -96,11 +99,13 @@ Buffer
 _hash_getbuf_with_condlock_cleanup(Relation rel, BlockNumber blkno, int flags)
 {
 	Buffer		buf;
+	bool        hit = false;
 
 	if (blkno == P_NEW)
 		elog(ERROR, "hash AM does not use P_NEW");
 
-	buf = ReadBuffer(rel, blkno);
+	buf = ReadBuffer(rel, blkno, &hit);
+	pgstat_count_index_buffer(rel, flags == LH_META_PAGE, hit);
 
 	if (!ConditionalLockBufferForCleanup(buf))
 	{
@@ -135,12 +140,14 @@ Buffer
 _hash_getinitbuf(Relation rel, BlockNumber blkno)
 {
 	Buffer		buf;
+	bool        hit = false;
 
 	if (blkno == P_NEW)
 		elog(ERROR, "hash AM does not use P_NEW");
 
 	buf = ReadBufferExtended(rel, MAIN_FORKNUM, blkno, RBM_ZERO_AND_LOCK,
-							 NULL);
+							 NULL, &hit);
+	pgstat_count_record_index_buffer(rel, hit);
 
 	/* ref count and lock type are correct */
 
@@ -199,6 +206,7 @@ _hash_getnewbuf(Relation rel, BlockNumber blkno, ForkNumber forkNum)
 {
 	BlockNumber nblocks = RelationGetNumberOfBlocksInFork(rel, forkNum);
 	Buffer		buf;
+	bool        hit = false;
 
 	if (blkno == P_NEW)
 		elog(ERROR, "hash AM does not use P_NEW");
@@ -218,7 +226,8 @@ _hash_getnewbuf(Relation rel, BlockNumber blkno, ForkNumber forkNum)
 	else
 	{
 		buf = ReadBufferExtended(rel, forkNum, blkno, RBM_ZERO_AND_LOCK,
-								 NULL);
+								 NULL, &hit);
+		pgstat_count_record_index_buffer(rel, hit);
 	}
 
 	/* ref count and lock type are correct */
@@ -241,11 +250,13 @@ _hash_getbuf_with_strategy(Relation rel, BlockNumber blkno,
 						   BufferAccessStrategy bstrategy)
 {
 	Buffer		buf;
+	bool        hit = false;
 
 	if (blkno == P_NEW)
 		elog(ERROR, "hash AM does not use P_NEW");
 
-	buf = ReadBufferExtended(rel, MAIN_FORKNUM, blkno, RBM_NORMAL, bstrategy);
+	buf = ReadBufferExtended(rel, MAIN_FORKNUM, blkno, RBM_NORMAL, bstrategy, &hit);
+	pgstat_count_index_buffer(rel, flags == LH_META_PAGE, hit);
 
 	if (access != HASH_NOLOCK)
 		LockBuffer(buf, access);
diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index ed2e302179..162620c029 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -1586,7 +1586,7 @@ heap_fetch(Relation relation,
 	/*
 	 * Fetch and pin the appropriate page of the relation.
 	 */
-	buffer = ReadBuffer(relation, ItemPointerGetBlockNumber(tid));
+	buffer = ReadBuffer(relation, ItemPointerGetBlockNumber(tid), NULL);
 
 	/*
 	 * Need share lock on buffer to examine tuple commit status.
@@ -1880,7 +1880,7 @@ heap_get_latest_tid(TableScanDesc sscan,
 		/*
 		 * Read, pin, and lock the page.
 		 */
-		buffer = ReadBuffer(relation, ItemPointerGetBlockNumber(&ctid));
+		buffer = ReadBuffer(relation, ItemPointerGetBlockNumber(&ctid), NULL);
 		LockBuffer(buffer, BUFFER_LOCK_SHARE);
 		page = BufferGetPage(buffer);
 
@@ -2776,7 +2776,7 @@ heap_delete(Relation relation, ItemPointer tid,
 				 errmsg("cannot delete tuples during a parallel operation")));
 
 	block = ItemPointerGetBlockNumber(tid);
-	buffer = ReadBuffer(relation, block);
+	buffer = ReadBuffer(relation, block, NULL);
 	page = BufferGetPage(buffer);
 
 	/*
@@ -3305,7 +3305,7 @@ heap_update(Relation relation, ItemPointer otid, HeapTuple newtup,
 
 	block = ItemPointerGetBlockNumber(otid);
 	INJECTION_POINT("heap_update-before-pin");
-	buffer = ReadBuffer(relation, block);
+	buffer = ReadBuffer(relation, block, NULL);
 	page = BufferGetPage(buffer);
 
 	/*
@@ -4561,7 +4561,7 @@ heap_lock_tuple(Relation relation, HeapTuple tuple,
 	bool		have_tuple_lock = false;
 	bool		cleared_all_frozen = false;
 
-	*buffer = ReadBuffer(relation, ItemPointerGetBlockNumber(tid));
+	*buffer = ReadBuffer(relation, ItemPointerGetBlockNumber(tid), NULL);
 	block = ItemPointerGetBlockNumber(tid);
 
 	/*
@@ -6057,7 +6057,7 @@ heap_finish_speculative(Relation relation, ItemPointer tid)
 	ItemId		lp = NULL;
 	HeapTupleHeader htup;
 
-	buffer = ReadBuffer(relation, ItemPointerGetBlockNumber(tid));
+	buffer = ReadBuffer(relation, ItemPointerGetBlockNumber(tid), NULL);
 	LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
 	page = (Page) BufferGetPage(buffer);
 
@@ -6148,7 +6148,7 @@ heap_abort_speculative(Relation relation, ItemPointer tid)
 	Assert(ItemPointerIsValid(tid));
 
 	block = ItemPointerGetBlockNumber(tid);
-	buffer = ReadBuffer(relation, block);
+	buffer = ReadBuffer(relation, block, NULL);
 	page = BufferGetPage(buffer);
 
 	LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
@@ -8231,7 +8231,7 @@ heap_index_delete_tuples(Relation rel, TM_IndexDeleteOp *delstate)
 				UnlockReleaseBuffer(buf);
 
 			blkno = ItemPointerGetBlockNumber(htid);
-			buf = ReadBuffer(rel, blkno);
+			buf = ReadBuffer(rel, blkno, NULL);
 			nblocksaccessed++;
 			Assert(!delstate->bottomup ||
 				   nblocksaccessed <= BOTTOMUP_MAX_NBLOCKS);
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index ac082fefa7..119528481c 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -132,7 +132,8 @@ heapam_index_fetch_tuple(struct IndexFetchTableData *scan,
 
 		hscan->xs_cbuf = ReleaseAndReadBuffer(hscan->xs_cbuf,
 											  hscan->xs_base.rel,
-											  ItemPointerGetBlockNumber(tid));
+											  ItemPointerGetBlockNumber(tid),
+											  NULL);
 
 		/*
 		 * Prune page, but only if we weren't already on this page
@@ -2250,7 +2251,8 @@ heapam_scan_sample_next_block(TableScanDesc scan, SampleScanState *scanstate)
 
 	/* Read page using selected strategy */
 	hscan->rs_cbuf = ReadBufferExtended(hscan->rs_base.rs_rd, MAIN_FORKNUM,
-										blockno, RBM_NORMAL, hscan->rs_strategy);
+										blockno, RBM_NORMAL, hscan->rs_strategy,
+										NULL);
 
 	/* in pagemode, prune the page and determine visible tuple offsets */
 	if (hscan->rs_base.rs_flags & SO_ALLOW_PAGEMODE)
diff --git a/src/backend/access/heap/hio.c b/src/backend/access/heap/hio.c
index c482c9d61b..7d5afcc6bc 100644
--- a/src/backend/access/heap/hio.c
+++ b/src/backend/access/heap/hio.c
@@ -93,7 +93,7 @@ ReadBufferBI(Relation relation, BlockNumber targetBlock,
 	/* If not bulk-insert, exactly like ReadBuffer */
 	if (!bistate)
 		return ReadBufferExtended(relation, MAIN_FORKNUM, targetBlock,
-								  mode, NULL);
+								  mode, NULL, NULL);
 
 	/* If we have the desired block already pinned, re-pin and return it */
 	if (bistate->current_buf != InvalidBuffer)
@@ -117,7 +117,7 @@ ReadBufferBI(Relation relation, BlockNumber targetBlock,
 
 	/* Perform a read using the buffer strategy */
 	buffer = ReadBufferExtended(relation, MAIN_FORKNUM, targetBlock,
-								mode, bistate->strategy);
+								mode, bistate->strategy, NULL);
 
 	/* Save the selected block as target for future inserts */
 	IncrBufferRefCount(buffer);
@@ -640,7 +640,7 @@ loop:
 		else if (otherBlock < targetBlock)
 		{
 			/* lock other buffer first */
-			buffer = ReadBuffer(relation, targetBlock);
+			buffer = ReadBuffer(relation, targetBlock, NULL);
 			if (PageIsAllVisible(BufferGetPage(buffer)))
 				visibilitymap_pin(relation, targetBlock, vmbuffer);
 			LockBuffer(otherBuffer, BUFFER_LOCK_EXCLUSIVE);
@@ -649,7 +649,7 @@ loop:
 		else
 		{
 			/* lock target buffer first */
-			buffer = ReadBuffer(relation, targetBlock);
+			buffer = ReadBuffer(relation, targetBlock, NULL);
 			if (PageIsAllVisible(BufferGetPage(buffer)))
 				visibilitymap_pin(relation, targetBlock, vmbuffer);
 			LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c
index f28326bad0..e935192466 100644
--- a/src/backend/access/heap/vacuumlazy.c
+++ b/src/backend/access/heap/vacuumlazy.c
@@ -3422,7 +3422,7 @@ count_nondeletable_pages(LVRelState *vacrel, bool *lock_waiter_detected)
 		}
 
 		buf = ReadBufferExtended(vacrel->rel, MAIN_FORKNUM, blkno, RBM_NORMAL,
-								 vacrel->bstrategy);
+								 vacrel->bstrategy, NULL);
 
 		/* In this phase we only need shared access to the buffer */
 		LockBuffer(buf, BUFFER_LOCK_SHARE);
diff --git a/src/backend/access/heap/visibilitymap.c b/src/backend/access/heap/visibilitymap.c
index 745a04ef26..a31f3098de 100644
--- a/src/backend/access/heap/visibilitymap.c
+++ b/src/backend/access/heap/visibilitymap.c
@@ -582,7 +582,7 @@ vm_readbuf(Relation rel, BlockNumber blkno, bool extend)
 	}
 	else
 		buf = ReadBufferExtended(rel, VISIBILITYMAP_FORKNUM, blkno,
-								 RBM_ZERO_ON_ERROR, NULL);
+								 RBM_ZERO_ON_ERROR, NULL, NULL);
 
 	/*
 	 * Initializing the page when needed is trickier than it looks, because of
diff --git a/src/backend/access/nbtree/nbtinsert.c b/src/backend/access/nbtree/nbtinsert.c
index aa82cede30..a9c83bb575 100644
--- a/src/backend/access/nbtree/nbtinsert.c
+++ b/src/backend/access/nbtree/nbtinsert.c
@@ -21,6 +21,7 @@
 #include "access/xloginsert.h"
 #include "common/int.h"
 #include "common/pg_prng.h"
+#include "pgstat.h"
 #include "lib/qunique.h"
 #include "miscadmin.h"
 #include "storage/lmgr.h"
@@ -323,7 +324,7 @@ _bt_search_insert(Relation rel, Relation heaprel, BTInsertState insertstate)
 	if (RelationGetTargetBlock(rel) != InvalidBlockNumber)
 	{
 		/* Simulate a _bt_getbuf() call with conditional locking */
-		insertstate->buf = ReadBuffer(rel, RelationGetTargetBlock(rel));
+		insertstate->buf = ReadBuffer(rel, RelationGetTargetBlock(rel), NULL);
 		if (_bt_conditionallockbuf(rel, insertstate->buf))
 		{
 			Page		page;
@@ -423,6 +424,7 @@ _bt_check_unique(Relation rel, BTInsertState insertstate, Relation heapRel,
 	bool		inposting = false;
 	bool		prevalldead = true;
 	int			curposti = 0;
+	bool        hit = false;
 
 	/* Assume unique until we find a duplicate */
 	*is_unique = true;
@@ -733,9 +735,10 @@ _bt_check_unique(Relation rel, BTInsertState insertstate, Relation heapRel,
 			{
 				BlockNumber nblkno = opaque->btpo_next;
 
-				nbuf = _bt_relandgetbuf(rel, nbuf, nblkno, BT_READ);
+				nbuf = _bt_relandgetbuf(rel, nbuf, nblkno, BT_READ, &hit);
 				page = BufferGetPage(nbuf);
 				opaque = BTPageGetOpaque(page);
+				pgstat_count_index_buffer(rel, !P_ISLEAF(opaque), hit);
 				if (!P_IGNORE(opaque))
 					break;
 				if (P_RIGHTMOST(opaque))
@@ -1040,7 +1043,9 @@ _bt_stepright(Relation rel, Relation heaprel, BTInsertState insertstate,
 	rblkno = opaque->btpo_next;
 	for (;;)
 	{
-		rbuf = _bt_relandgetbuf(rel, rbuf, rblkno, BT_WRITE);
+		bool hit = false;
+		rbuf = _bt_relandgetbuf(rel, rbuf, rblkno, BT_WRITE, &hit);
+		pgstat_count_index_buffer(rel, !P_ISLEAF(opaque), hit);
 		page = BufferGetPage(rbuf);
 		opaque = BTPageGetOpaque(page);
 
@@ -1256,10 +1261,13 @@ _bt_insertonpg(Relation rel,
 		 */
 		if (unlikely(split_only_page))
 		{
+			bool hit = false;
+
 			Assert(!isleaf);
 			Assert(BufferIsValid(cbuf));
 
-			metabuf = _bt_getbuf(rel, BTREE_METAPAGE, BT_WRITE);
+			metabuf = _bt_getbuf(rel, BTREE_METAPAGE, BT_WRITE, &hit);
+			pgstat_count_metadata_index_buffer(rel, hit);
 			metapg = BufferGetPage(metabuf);
 			metad = BTPageGetMeta(metapg);
 
@@ -1890,7 +1898,9 @@ _bt_split(Relation rel, Relation heaprel, BTScanInsert itup_key, Buffer buf,
 	 */
 	if (!isrightmost)
 	{
-		sbuf = _bt_getbuf(rel, oopaque->btpo_next, BT_WRITE);
+		bool hit = false;
+		sbuf = _bt_getbuf(rel, oopaque->btpo_next, BT_WRITE, &hit);
+		pgstat_count_index_buffer(rel, !P_ISLEAF(oopaque), hit);
 		spage = BufferGetPage(sbuf);
 		sopaque = BTPageGetOpaque(spage);
 		if (sopaque->btpo_prev != origpagenumber)
@@ -2247,12 +2257,14 @@ _bt_finish_split(Relation rel, Relation heaprel, Buffer lbuf, BTStack stack)
 	BTPageOpaque rpageop;
 	bool		wasroot;
 	bool		wasonly;
+	bool        hit = false;
 
 	Assert(P_INCOMPLETE_SPLIT(lpageop));
 	Assert(heaprel != NULL);
 
 	/* Lock right sibling, the one missing the downlink */
-	rbuf = _bt_getbuf(rel, lpageop->btpo_next, BT_WRITE);
+	rbuf = _bt_getbuf(rel, lpageop->btpo_next, BT_WRITE, &hit);
+	pgstat_count_index_buffer(rel, !P_ISLEAF(lpageop), hit);
 	rpage = BufferGetPage(rbuf);
 	rpageop = BTPageGetOpaque(rpage);
 
@@ -2264,7 +2276,8 @@ _bt_finish_split(Relation rel, Relation heaprel, Buffer lbuf, BTStack stack)
 		BTMetaPageData *metad;
 
 		/* acquire lock on the metapage */
-		metabuf = _bt_getbuf(rel, BTREE_METAPAGE, BT_WRITE);
+		metabuf = _bt_getbuf(rel, BTREE_METAPAGE, BT_WRITE, &hit);
+		pgstat_count_metadata_index_buffer(rel, hit);
 		metapg = BufferGetPage(metabuf);
 		metad = BTPageGetMeta(metapg);
 
@@ -2320,6 +2333,7 @@ _bt_getstackbuf(Relation rel, Relation heaprel, BTStack stack, BlockNumber child
 {
 	BlockNumber blkno;
 	OffsetNumber start;
+	bool         hit = false;
 
 	blkno = stack->bts_blkno;
 	start = stack->bts_offset;
@@ -2330,9 +2344,10 @@ _bt_getstackbuf(Relation rel, Relation heaprel, BTStack stack, BlockNumber child
 		Page		page;
 		BTPageOpaque opaque;
 
-		buf = _bt_getbuf(rel, blkno, BT_WRITE);
+		buf = _bt_getbuf(rel, blkno, BT_WRITE, &hit);
 		page = BufferGetPage(buf);
 		opaque = BTPageGetOpaque(page);
+		pgstat_count_index_buffer(rel, !P_ISLEAF(opaque), hit);
 
 		Assert(heaprel != NULL);
 		if (P_INCOMPLETE_SPLIT(opaque))
@@ -2460,6 +2475,7 @@ _bt_newlevel(Relation rel, Relation heaprel, Buffer lbuf, Buffer rbuf)
 	Buffer		metabuf;
 	Page		metapg;
 	BTMetaPageData *metad;
+	bool           hit = false;
 
 	lbkno = BufferGetBlockNumber(lbuf);
 	rbkno = BufferGetBlockNumber(rbuf);
@@ -2472,9 +2488,10 @@ _bt_newlevel(Relation rel, Relation heaprel, Buffer lbuf, Buffer rbuf)
 	rootblknum = BufferGetBlockNumber(rootbuf);
 
 	/* acquire lock on the metapage */
-	metabuf = _bt_getbuf(rel, BTREE_METAPAGE, BT_WRITE);
+	metabuf = _bt_getbuf(rel, BTREE_METAPAGE, BT_WRITE, &hit);
 	metapg = BufferGetPage(metabuf);
 	metad = BTPageGetMeta(metapg);
+	pgstat_count_metadata_index_buffer(rel, hit);
 
 	/*
 	 * Create downlink item for left page (old root).  The key value used is
diff --git a/src/backend/access/nbtree/nbtpage.c b/src/backend/access/nbtree/nbtpage.c
index c79dd38ee1..2c7183a41b 100644
--- a/src/backend/access/nbtree/nbtpage.c
+++ b/src/backend/access/nbtree/nbtpage.c
@@ -30,6 +30,7 @@
 #include "access/xloginsert.h"
 #include "common/int.h"
 #include "miscadmin.h"
+#include "pgstat.h"
 #include "storage/indexfsm.h"
 #include "storage/predicate.h"
 #include "storage/procarray.h"
@@ -183,13 +184,15 @@ _bt_vacuum_needs_cleanup(Relation rel)
 	BTMetaPageData *metad;
 	uint32		btm_version;
 	BlockNumber prev_num_delpages;
+	bool        hit = false;
 
 	/*
 	 * Copy details from metapage to local variables quickly.
 	 *
 	 * Note that we deliberately avoid using cached version of metapage here.
 	 */
-	metabuf = _bt_getbuf(rel, BTREE_METAPAGE, BT_READ);
+	metabuf = _bt_getbuf(rel, BTREE_METAPAGE, BT_READ, &hit);
+	pgstat_count_metadata_index_buffer(rel, hit);
 	metapg = BufferGetPage(metabuf);
 	metad = BTPageGetMeta(metapg);
 	btm_version = metad->btm_version;
@@ -234,6 +237,7 @@ _bt_set_cleanup_info(Relation rel, BlockNumber num_delpages)
 	Buffer		metabuf;
 	Page		metapg;
 	BTMetaPageData *metad;
+	bool           hit = false;
 
 	/*
 	 * On-disk compatibility note: The btm_last_cleanup_num_delpages metapage
@@ -253,7 +257,8 @@ _bt_set_cleanup_info(Relation rel, BlockNumber num_delpages)
 	 * no longer used as of PostgreSQL 14.  We set it to -1.0 on rewrite, just
 	 * to be consistent.
 	 */
-	metabuf = _bt_getbuf(rel, BTREE_METAPAGE, BT_READ);
+	metabuf = _bt_getbuf(rel, BTREE_METAPAGE, BT_READ, &hit);
+	pgstat_count_metadata_index_buffer(rel, hit);
 	metapg = BufferGetPage(metabuf);
 	metad = BTPageGetMeta(metapg);
 
@@ -350,6 +355,7 @@ _bt_getroot(Relation rel, Relation heaprel, int access)
 	BlockNumber rootblkno;
 	uint32		rootlevel;
 	BTMetaPageData *metad;
+	bool           hit = false;
 
 	Assert(access == BT_READ || heaprel != NULL);
 
@@ -373,7 +379,8 @@ _bt_getroot(Relation rel, Relation heaprel, int access)
 		Assert(rootblkno != P_NONE);
 		rootlevel = metad->btm_fastlevel;
 
-		rootbuf = _bt_getbuf(rel, rootblkno, BT_READ);
+		rootbuf = _bt_getbuf(rel, rootblkno, BT_READ, &hit);
+		pgstat_count_metadata_index_buffer(rel, hit);
 		rootpage = BufferGetPage(rootbuf);
 		rootopaque = BTPageGetOpaque(rootpage);
 
@@ -399,7 +406,8 @@ _bt_getroot(Relation rel, Relation heaprel, int access)
 		rel->rd_amcache = NULL;
 	}
 
-	metabuf = _bt_getbuf(rel, BTREE_METAPAGE, BT_READ);
+	metabuf = _bt_getbuf(rel, BTREE_METAPAGE, BT_READ, &hit);
+	pgstat_count_metadata_index_buffer(rel, hit);
 	metad = _bt_getmeta(rel, metabuf);
 
 	/* if no root page initialized yet, do it */
@@ -535,7 +543,8 @@ _bt_getroot(Relation rel, Relation heaprel, int access)
 
 		for (;;)
 		{
-			rootbuf = _bt_relandgetbuf(rel, rootbuf, rootblkno, BT_READ);
+			rootbuf = _bt_relandgetbuf(rel, rootbuf, rootblkno, BT_READ, &hit);
+			pgstat_count_metadata_index_buffer(rel, hit);
 			rootpage = BufferGetPage(rootbuf);
 			rootopaque = BTPageGetOpaque(rootpage);
 
@@ -588,6 +597,7 @@ _bt_gettrueroot(Relation rel)
 	BlockNumber rootblkno;
 	uint32		rootlevel;
 	BTMetaPageData *metad;
+	bool           hit = false;
 
 	/*
 	 * We don't try to use cached metapage data here, since (a) this path is
@@ -599,7 +609,8 @@ _bt_gettrueroot(Relation rel)
 		pfree(rel->rd_amcache);
 	rel->rd_amcache = NULL;
 
-	metabuf = _bt_getbuf(rel, BTREE_METAPAGE, BT_READ);
+	metabuf = _bt_getbuf(rel, BTREE_METAPAGE, BT_READ, &hit);
+	pgstat_count_metadata_index_buffer(rel, hit);
 	metapg = BufferGetPage(metabuf);
 	metaopaque = BTPageGetOpaque(metapg);
 	metad = BTPageGetMeta(metapg);
@@ -638,7 +649,8 @@ _bt_gettrueroot(Relation rel)
 
 	for (;;)
 	{
-		rootbuf = _bt_relandgetbuf(rel, rootbuf, rootblkno, BT_READ);
+		rootbuf = _bt_relandgetbuf(rel, rootbuf, rootblkno, BT_READ, &hit);
+		pgstat_count_metadata_index_buffer(rel, hit);
 		rootpage = BufferGetPage(rootbuf);
 		rootopaque = BTPageGetOpaque(rootpage);
 
@@ -679,8 +691,10 @@ _bt_getrootheight(Relation rel)
 	if (rel->rd_amcache == NULL)
 	{
 		Buffer		metabuf;
+		bool        hit = false;
 
-		metabuf = _bt_getbuf(rel, BTREE_METAPAGE, BT_READ);
+		metabuf = _bt_getbuf(rel, BTREE_METAPAGE, BT_READ, &hit);
+		pgstat_count_metadata_index_buffer(rel, hit);
 		metad = _bt_getmeta(rel, metabuf);
 
 		/*
@@ -743,8 +757,10 @@ _bt_metaversion(Relation rel, bool *heapkeyspace, bool *allequalimage)
 	if (rel->rd_amcache == NULL)
 	{
 		Buffer		metabuf;
+		bool 		hit = false;
 
-		metabuf = _bt_getbuf(rel, BTREE_METAPAGE, BT_READ);
+		metabuf = _bt_getbuf(rel, BTREE_METAPAGE, BT_READ, &hit);
+		pgstat_count_metadata_index_buffer(rel, hit);
 		metad = _bt_getmeta(rel, metabuf);
 
 		/*
@@ -842,14 +858,14 @@ _bt_checkpage(Relation rel, Buffer buf)
  *		as _bt_lockbuf().
  */
 Buffer
-_bt_getbuf(Relation rel, BlockNumber blkno, int access)
+_bt_getbuf(Relation rel, BlockNumber blkno, int access, bool *hit)
 {
 	Buffer		buf;
 
 	Assert(BlockNumberIsValid(blkno));
 
 	/* Read an existing block of the relation */
-	buf = ReadBuffer(rel, blkno);
+	buf = ReadBuffer(rel, blkno, hit);
 	_bt_lockbuf(rel, buf, access);
 	_bt_checkpage(rel, buf);
 
@@ -903,7 +919,7 @@ _bt_allocbuf(Relation rel, Relation heaprel)
 		blkno = GetFreeIndexPage(rel);
 		if (blkno == InvalidBlockNumber)
 			break;
-		buf = ReadBuffer(rel, blkno);
+		buf = ReadBuffer(rel, blkno, NULL);
 		if (_bt_conditionallockbuf(rel, buf))
 		{
 			page = BufferGetPage(buf);
@@ -1000,14 +1016,14 @@ _bt_allocbuf(Relation rel, Relation heaprel)
  * is when the target page is the same one already in the buffer.
  */
 Buffer
-_bt_relandgetbuf(Relation rel, Buffer obuf, BlockNumber blkno, int access)
+_bt_relandgetbuf(Relation rel, Buffer obuf, BlockNumber blkno, int access, bool *hit)
 {
 	Buffer		buf;
 
 	Assert(BlockNumberIsValid(blkno));
 	if (BufferIsValid(obuf))
 		_bt_unlockbuf(rel, obuf);
-	buf = ReleaseAndReadBuffer(obuf, rel, blkno);
+	buf = ReleaseAndReadBuffer(obuf, rel, blkno, hit);
 	_bt_lockbuf(rel, buf, access);
 
 	_bt_checkpage(rel, buf);
@@ -1698,14 +1714,16 @@ _bt_leftsib_splitflag(Relation rel, BlockNumber leftsib, BlockNumber target)
 	Page		page;
 	BTPageOpaque opaque;
 	bool		result;
+	bool        hit = false;
 
 	/* Easy case: No left sibling */
 	if (leftsib == P_NONE)
 		return false;
 
-	buf = _bt_getbuf(rel, leftsib, BT_READ);
+	buf = _bt_getbuf(rel, leftsib, BT_READ, &hit);
 	page = BufferGetPage(buf);
 	opaque = BTPageGetOpaque(page);
+	pgstat_count_index_buffer(rel, !P_ISLEAF(opaque), hit);
 
 	/*
 	 * If the left sibling was concurrently split, so that its next-pointer
@@ -1758,7 +1776,7 @@ _bt_rightsib_halfdeadflag(Relation rel, BlockNumber leafrightsib)
 
 	Assert(leafrightsib != P_NONE);
 
-	buf = _bt_getbuf(rel, leafrightsib, BT_READ);
+	buf = _bt_getbuf(rel, leafrightsib, BT_READ, NULL);
 	page = BufferGetPage(buf);
 	opaque = BTPageGetOpaque(page);
 
@@ -2062,7 +2080,7 @@ _bt_pagedel(Relation rel, Buffer leafbuf, BTVacState *vstate)
 		if (!rightsib_empty)
 			break;
 
-		leafbuf = _bt_getbuf(rel, rightsib, BT_WRITE);
+		leafbuf = _bt_getbuf(rel, rightsib, BT_WRITE, NULL);
 	}
 }
 
@@ -2335,6 +2353,7 @@ _bt_unlink_halfdead_page(Relation rel, Buffer leafbuf, BlockNumber scanblkno,
 	uint32		targetlevel;
 	IndexTuple	leafhikey;
 	BlockNumber leaftopparent;
+	bool        hit = false;
 
 	page = BufferGetPage(leafbuf);
 	opaque = BTPageGetOpaque(page);
@@ -2374,7 +2393,8 @@ _bt_unlink_halfdead_page(Relation rel, Buffer leafbuf, BlockNumber scanblkno,
 		Assert(target != leafblkno);
 
 		/* Fetch the block number of the target's left sibling */
-		buf = _bt_getbuf(rel, target, BT_READ);
+		buf = _bt_getbuf(rel, target, BT_READ, &hit);
+		pgstat_count_metadata_index_buffer(rel, hit);
 		page = BufferGetPage(buf);
 		opaque = BTPageGetOpaque(page);
 		leftsib = opaque->btpo_prev;
@@ -2401,7 +2421,7 @@ _bt_unlink_halfdead_page(Relation rel, Buffer leafbuf, BlockNumber scanblkno,
 		_bt_lockbuf(rel, leafbuf, BT_WRITE);
 	if (leftsib != P_NONE)
 	{
-		lbuf = _bt_getbuf(rel, leftsib, BT_WRITE);
+		lbuf = _bt_getbuf(rel, leftsib, BT_WRITE, NULL);
 		page = BufferGetPage(lbuf);
 		opaque = BTPageGetOpaque(page);
 		while (P_ISDELETED(opaque) || opaque->btpo_next != target)
@@ -2449,7 +2469,8 @@ _bt_unlink_halfdead_page(Relation rel, Buffer leafbuf, BlockNumber scanblkno,
 			CHECK_FOR_INTERRUPTS();
 
 			/* step right one page */
-			lbuf = _bt_getbuf(rel, leftsib, BT_WRITE);
+			lbuf = _bt_getbuf(rel, leftsib, BT_WRITE, &hit);
+			pgstat_count_index_buffer(rel, !P_ISLEAF(opaque), hit);
 			page = BufferGetPage(lbuf);
 			opaque = BTPageGetOpaque(page);
 		}
@@ -2513,7 +2534,8 @@ _bt_unlink_halfdead_page(Relation rel, Buffer leafbuf, BlockNumber scanblkno,
 	 * And next write-lock the (current) right sibling.
 	 */
 	rightsib = opaque->btpo_next;
-	rbuf = _bt_getbuf(rel, rightsib, BT_WRITE);
+	rbuf = _bt_getbuf(rel, rightsib, BT_WRITE, &hit);
+	pgstat_count_index_buffer(rel, !P_ISLEAF(opaque), hit);
 	page = BufferGetPage(rbuf);
 	opaque = BTPageGetOpaque(page);
 
@@ -2569,7 +2591,8 @@ _bt_unlink_halfdead_page(Relation rel, Buffer leafbuf, BlockNumber scanblkno,
 		if (P_RIGHTMOST(opaque))
 		{
 			/* rightsib will be the only one left on the level */
-			metabuf = _bt_getbuf(rel, BTREE_METAPAGE, BT_WRITE);
+			metabuf = _bt_getbuf(rel, BTREE_METAPAGE, BT_WRITE, &hit);
+			pgstat_count_metadata_index_buffer(rel, hit);
 			metapg = BufferGetPage(metabuf);
 			metad = BTPageGetMeta(metapg);
 
diff --git a/src/backend/access/nbtree/nbtree.c b/src/backend/access/nbtree/nbtree.c
index accc7fe8bb..18afe6ec45 100644
--- a/src/backend/access/nbtree/nbtree.c
+++ b/src/backend/access/nbtree/nbtree.c
@@ -1641,7 +1641,7 @@ backtrack:
 		 * nondefault buffer access strategy.
 		 */
 		buf = ReadBufferExtended(rel, MAIN_FORKNUM, blkno, RBM_NORMAL,
-								 info->strategy);
+								 info->strategy, NULL);
 		goto backtrack;
 	}
 
diff --git a/src/backend/access/nbtree/nbtsearch.c b/src/backend/access/nbtree/nbtsearch.c
index f69397623d..30d10bff4b 100644
--- a/src/backend/access/nbtree/nbtsearch.c
+++ b/src/backend/access/nbtree/nbtsearch.c
@@ -126,6 +126,7 @@ _bt_search(Relation rel, Relation heaprel, BTScanInsert key, Buffer *bufP,
 		IndexTuple	itup;
 		BlockNumber child;
 		BTStack		new_stack;
+		bool        hit = false;
 
 		/*
 		 * Race -- the page we just grabbed may have split since we read its
@@ -178,7 +179,8 @@ _bt_search(Relation rel, Relation heaprel, BTScanInsert key, Buffer *bufP,
 			page_access = BT_WRITE;
 
 		/* drop the read lock on the page, then acquire one on its child */
-		*bufP = _bt_relandgetbuf(rel, *bufP, child, page_access);
+		*bufP = _bt_relandgetbuf(rel, *bufP, child, page_access, &hit);
+		pgstat_count_index_buffer(rel, opaque->btpo_level != 1, hit);
 
 		/* okay, all set to move down a level */
 		stack_in = new_stack;
@@ -249,6 +251,7 @@ _bt_moveright(Relation rel,
 	Page		page;
 	BTPageOpaque opaque;
 	int32		cmpval;
+	bool        hit = false;
 
 	Assert(!forupdate || heaprel != NULL);
 
@@ -299,14 +302,16 @@ _bt_moveright(Relation rel,
 				_bt_relbuf(rel, buf);
 
 			/* re-acquire the lock in the right mode, and re-check */
-			buf = _bt_getbuf(rel, blkno, access);
+			buf = _bt_getbuf(rel, blkno, access, &hit);
+			pgstat_count_index_buffer(rel, !P_ISLEAF(opaque), hit);
 			continue;
 		}
 
 		if (P_IGNORE(opaque) || _bt_compare(rel, key, page, P_HIKEY) >= cmpval)
 		{
 			/* step right one page */
-			buf = _bt_relandgetbuf(rel, buf, opaque->btpo_next, access);
+			buf = _bt_relandgetbuf(rel, buf, opaque->btpo_next, access, &hit);
+			pgstat_count_index_buffer(rel, !P_ISLEAF(opaque), hit);
 			continue;
 		}
 		else
@@ -2301,6 +2306,7 @@ static bool
 _bt_readnextpage(IndexScanDesc scan, BlockNumber blkno,
 				 BlockNumber lastcurrblkno, ScanDirection dir, bool seized)
 {
+	bool        hit = false;
 	Relation	rel = scan->indexRelation;
 	BTScanOpaque so = (BTScanOpaque) scan->opaque;
 
@@ -2348,7 +2354,8 @@ _bt_readnextpage(IndexScanDesc scan, BlockNumber blkno,
 		{
 			/* read blkno, but check for interrupts first */
 			CHECK_FOR_INTERRUPTS();
-			so->currPos.buf = _bt_getbuf(rel, blkno, BT_READ);
+			so->currPos.buf = _bt_getbuf(rel, blkno, BT_READ, &hit);
+			pgstat_count_record_index_buffer(rel, hit);
 		}
 		else
 		{
@@ -2444,10 +2451,11 @@ _bt_lock_and_validate_left(Relation rel, BlockNumber *blkno,
 		Page		page;
 		BTPageOpaque opaque;
 		int			tries;
+		bool        hit = false;
 
 		/* check for interrupts while we're not holding any buffer lock */
 		CHECK_FOR_INTERRUPTS();
-		buf = _bt_getbuf(rel, *blkno, BT_READ);
+		buf = _bt_getbuf(rel, *blkno, BT_READ, &hit);
 		page = BufferGetPage(buf);
 		opaque = BTPageGetOpaque(page);
 
@@ -2474,7 +2482,8 @@ _bt_lock_and_validate_left(Relation rel, BlockNumber *blkno,
 				break;
 			/* step right */
 			*blkno = opaque->btpo_next;
-			buf = _bt_relandgetbuf(rel, buf, *blkno, BT_READ);
+			buf = _bt_relandgetbuf(rel, buf, *blkno, BT_READ, &hit);
+			pgstat_count_index_buffer(rel, !P_ISLEAF(opaque), hit);
 			page = BufferGetPage(buf);
 			opaque = BTPageGetOpaque(page);
 		}
@@ -2484,9 +2493,10 @@ _bt_lock_and_validate_left(Relation rel, BlockNumber *blkno,
 		 * _bt_readpage, which is passed by caller as lastcurrblkno) to see
 		 * what's up with its prev sibling link
 		 */
-		buf = _bt_relandgetbuf(rel, buf, lastcurrblkno, BT_READ);
+		buf = _bt_relandgetbuf(rel, buf, lastcurrblkno, BT_READ, &hit);
 		page = BufferGetPage(buf);
 		opaque = BTPageGetOpaque(page);
+		pgstat_count_index_buffer(rel, !P_ISLEAF(opaque), hit);
 		if (P_ISDELETED(opaque))
 		{
 			/*
@@ -2501,7 +2511,8 @@ _bt_lock_and_validate_left(Relation rel, BlockNumber *blkno,
 					elog(ERROR, "fell off the end of index \"%s\"",
 						 RelationGetRelationName(rel));
 				lastcurrblkno = opaque->btpo_next;
-				buf = _bt_relandgetbuf(rel, buf, lastcurrblkno, BT_READ);
+				buf = _bt_relandgetbuf(rel, buf, lastcurrblkno, BT_READ, &hit);
+				pgstat_count_index_buffer(rel, !P_ISLEAF(opaque), hit);
 				page = BufferGetPage(buf);
 				opaque = BTPageGetOpaque(page);
 				if (!P_ISDELETED(opaque))
@@ -2558,6 +2569,7 @@ _bt_get_endpoint(Relation rel, uint32 level, bool rightmost)
 	OffsetNumber offnum;
 	BlockNumber blkno;
 	IndexTuple	itup;
+	bool        hit = false;
 
 	/*
 	 * If we are looking for a leaf page, okay to descend from fast root;
@@ -2590,7 +2602,8 @@ _bt_get_endpoint(Relation rel, uint32 level, bool rightmost)
 			if (blkno == P_NONE)
 				elog(ERROR, "fell off the end of index \"%s\"",
 					 RelationGetRelationName(rel));
-			buf = _bt_relandgetbuf(rel, buf, blkno, BT_READ);
+			buf = _bt_relandgetbuf(rel, buf, blkno, BT_READ, &hit);
+			pgstat_count_record_index_buffer(rel, hit);
 			page = BufferGetPage(buf);
 			opaque = BTPageGetOpaque(page);
 		}
@@ -2613,7 +2626,8 @@ _bt_get_endpoint(Relation rel, uint32 level, bool rightmost)
 		itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, offnum));
 		blkno = BTreeTupleGetDownLink(itup);
 
-		buf = _bt_relandgetbuf(rel, buf, blkno, BT_READ);
+		buf = _bt_relandgetbuf(rel, buf, blkno, BT_READ, &hit);
+		pgstat_count_record_index_buffer(rel, hit);
 		page = BufferGetPage(buf);
 		opaque = BTPageGetOpaque(page);
 	}
diff --git a/src/backend/access/nbtree/nbtutils.c b/src/backend/access/nbtree/nbtutils.c
index 9e27302fe8..c705559362 100644
--- a/src/backend/access/nbtree/nbtutils.c
+++ b/src/backend/access/nbtree/nbtutils.c
@@ -20,6 +20,7 @@
 #include "access/nbtree.h"
 #include "access/reloptions.h"
 #include "commands/progress.h"
+#include "pgstat.h"
 #include "miscadmin.h"
 #include "utils/datum.h"
 #include "utils/lsyscache.h"
@@ -3318,6 +3319,7 @@ _bt_killitems(IndexScanDesc scan)
 	int			numKilled = so->numKilled;
 	bool		killedsomething = false;
 	bool		droppedpin PG_USED_FOR_ASSERTS_ONLY;
+	bool        hit = false;
 
 	Assert(BTScanPosIsValid(so->currPos));
 
@@ -3346,9 +3348,10 @@ _bt_killitems(IndexScanDesc scan)
 
 		droppedpin = true;
 		/* Attempt to re-read the buffer, getting pin and lock. */
-		buf = _bt_getbuf(scan->indexRelation, so->currPos.currPage, BT_READ);
+		buf = _bt_getbuf(scan->indexRelation, so->currPos.currPage, BT_READ, &hit);
 
 		page = BufferGetPage(buf);
+		pgstat_count_index_buffer(scan->indexRelation, !P_ISLEAF(BTPageGetOpaque(page)), hit);
 		if (BufferGetLSNAtomic(buf) == so->currPos.lsn)
 			so->currPos.buf = buf;
 		else
diff --git a/src/backend/access/spgist/spgdoinsert.c b/src/backend/access/spgist/spgdoinsert.c
index af6b27b213..7673162c5a 100644
--- a/src/backend/access/spgist/spgdoinsert.c
+++ b/src/backend/access/spgist/spgdoinsert.c
@@ -21,6 +21,7 @@
 #include "access/xloginsert.h"
 #include "common/int.h"
 #include "common/pg_prng.h"
+#include "pgstat.h"
 #include "miscadmin.h"
 #include "storage/bufmgr.h"
 #include "utils/rel.h"
@@ -1925,6 +1926,7 @@ spgdoinsert(Relation index, SpGistState *state,
 	SPPageDesc	current,
 				parent;
 	FmgrInfo   *procinfo = NULL;
+	bool        hit = false;
 
 	/*
 	 * Look up FmgrInfo of the user-defined choose function once, to save
@@ -2065,13 +2067,15 @@ spgdoinsert(Relation index, SpGistState *state,
 		else if (parent.buffer == InvalidBuffer)
 		{
 			/* we hold no parent-page lock, so no deadlock is possible */
-			current.buffer = ReadBuffer(index, current.blkno);
+			current.buffer = ReadBuffer(index, current.blkno, &hit);
+			pgstat_count_record_index_buffer(index, hit);
 			LockBuffer(current.buffer, BUFFER_LOCK_EXCLUSIVE);
 		}
 		else if (current.blkno != parent.blkno)
 		{
 			/* descend to a new child page */
-			current.buffer = ReadBuffer(index, current.blkno);
+			current.buffer = ReadBuffer(index, current.blkno, &hit);
+			pgstat_count_record_index_buffer(index, hit);
 
 			/*
 			 * Attempt to acquire lock on child page.  We must beware of
diff --git a/src/backend/access/spgist/spgscan.c b/src/backend/access/spgist/spgscan.c
index 25893050c5..49976f03ff 100644
--- a/src/backend/access/spgist/spgscan.c
+++ b/src/backend/access/spgist/spgscan.c
@@ -846,16 +846,18 @@ redirect:
 			OffsetNumber offset = ItemPointerGetOffsetNumber(&item->heapPtr);
 			Page		page;
 			bool		isnull;
+			bool        hit = false;
 
 			if (buffer == InvalidBuffer)
 			{
-				buffer = ReadBuffer(index, blkno);
+				buffer = ReadBuffer(index, blkno, &hit);
+
 				LockBuffer(buffer, BUFFER_LOCK_SHARE);
 			}
 			else if (blkno != BufferGetBlockNumber(buffer))
 			{
 				UnlockReleaseBuffer(buffer);
-				buffer = ReadBuffer(index, blkno);
+				buffer = ReadBuffer(index, blkno, &hit);
 				LockBuffer(buffer, BUFFER_LOCK_SHARE);
 			}
 
@@ -869,6 +871,7 @@ redirect:
 			{
 				/* Page is a leaf - that is, all its tuples are heap items */
 				OffsetNumber max = PageGetMaxOffsetNumber(page);
+				pgstat_count_record_index_buffer(index, hit);
 
 				if (SpGistBlockIsRoot(blkno))
 				{
@@ -897,6 +900,7 @@ redirect:
 				SpGistInnerTuple innerTuple = (SpGistInnerTuple)
 					PageGetItem(page, PageGetItemId(page, offset));
 
+				pgstat_count_metadata_index_buffer(index, hit);
 				if (innerTuple->tupstate != SPGIST_LIVE)
 				{
 					if (innerTuple->tupstate == SPGIST_REDIRECT)
diff --git a/src/backend/access/spgist/spgutils.c b/src/backend/access/spgist/spgutils.c
index 95fea74e29..6f6876df47 100644
--- a/src/backend/access/spgist/spgutils.c
+++ b/src/backend/access/spgist/spgutils.c
@@ -26,6 +26,7 @@
 #include "commands/vacuum.h"
 #include "nodes/nodeFuncs.h"
 #include "parser/parse_coerce.h"
+#include "pgstat.h"
 #include "storage/bufmgr.h"
 #include "storage/indexfsm.h"
 #include "utils/catcache.h"
@@ -269,8 +270,10 @@ spgGetCache(Relation index)
 		{
 			Buffer		metabuffer;
 			SpGistMetaPageData *metadata;
+			bool               hit = false;
 
-			metabuffer = ReadBuffer(index, SPGIST_METAPAGE_BLKNO);
+			metabuffer = ReadBuffer(index, SPGIST_METAPAGE_BLKNO, &hit);
+			pgstat_count_metadata_index_buffer(index, hit);
 			LockBuffer(metabuffer, BUFFER_LOCK_SHARE);
 
 			metadata = SpGistPageGetMeta(BufferGetPage(metabuffer));
@@ -394,6 +397,7 @@ Buffer
 SpGistNewBuffer(Relation index)
 {
 	Buffer		buffer;
+	bool        hit = false;
 
 	/* First, try to get a page from FSM */
 	for (;;)
@@ -410,7 +414,7 @@ SpGistNewBuffer(Relation index)
 		if (SpGistBlockIsFixed(blkno))
 			continue;
 
-		buffer = ReadBuffer(index, blkno);
+		buffer = ReadBuffer(index, blkno, &hit);
 
 		/*
 		 * We have to guard against the possibility that someone else already
@@ -419,6 +423,7 @@ SpGistNewBuffer(Relation index)
 		if (ConditionalLockBuffer(buffer))
 		{
 			Page		page = BufferGetPage(buffer);
+			pgstat_count_record_index_buffer(index, hit);
 
 			if (PageIsNew(page))
 				return buffer;	/* OK to use, if never initialized */
@@ -449,13 +454,15 @@ SpGistNewBuffer(Relation index)
 void
 SpGistUpdateMetaPage(Relation index)
 {
+	bool hit = false;
 	SpGistCache *cache = (SpGistCache *) index->rd_amcache;
 
 	if (cache != NULL)
 	{
 		Buffer		metabuffer;
 
-		metabuffer = ReadBuffer(index, SPGIST_METAPAGE_BLKNO);
+		metabuffer = ReadBuffer(index, SPGIST_METAPAGE_BLKNO, &hit);
+		pgstat_count_metadata_index_buffer(index, hit);
 
 		if (ConditionalLockBuffer(metabuffer))
 		{
@@ -568,6 +575,7 @@ allocNewBuffer(Relation index, int flags)
 Buffer
 SpGistGetBuffer(Relation index, int flags, int needSpace, bool *isNew)
 {
+	bool hit = false;
 	SpGistCache *cache = spgGetCache(index);
 	SpGistLastUsedPage *lup;
 
@@ -604,7 +612,7 @@ SpGistGetBuffer(Relation index, int flags, int needSpace, bool *isNew)
 		Buffer		buffer;
 		Page		page;
 
-		buffer = ReadBuffer(index, lup->blkno);
+		buffer = ReadBuffer(index, lup->blkno, &hit);
 
 		if (!ConditionalLockBuffer(buffer))
 		{
@@ -617,6 +625,7 @@ SpGistGetBuffer(Relation index, int flags, int needSpace, bool *isNew)
 		}
 
 		page = BufferGetPage(buffer);
+		pgstat_count_record_index_buffer(index, hit);
 
 		if (PageIsNew(page) || SpGistPageIsDeleted(page) || PageIsEmpty(page))
 		{
diff --git a/src/backend/access/spgist/spgvacuum.c b/src/backend/access/spgist/spgvacuum.c
index 81171f3545..2161d69452 100644
--- a/src/backend/access/spgist/spgvacuum.c
+++ b/src/backend/access/spgist/spgvacuum.c
@@ -705,7 +705,7 @@ spgprocesspending(spgBulkDeleteState *bds)
 		/* examine the referenced page */
 		blkno = ItemPointerGetBlockNumber(&pitem->tid);
 		buffer = ReadBufferExtended(index, MAIN_FORKNUM, blkno,
-									RBM_NORMAL, bds->info->strategy);
+									RBM_NORMAL, bds->info->strategy, NULL);
 		LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
 		page = (Page) BufferGetPage(buffer);
 
diff --git a/src/backend/access/transam/xloginsert.c b/src/backend/access/transam/xloginsert.c
index 5ee9d0b028..dd71a1d0c6 100644
--- a/src/backend/access/transam/xloginsert.c
+++ b/src/backend/access/transam/xloginsert.c
@@ -1300,7 +1300,7 @@ log_newpage_range(Relation rel, ForkNumber forknum,
 		while (nbufs < XLR_MAX_BLOCK_ID && blkno < endblk)
 		{
 			Buffer		buf = ReadBufferExtended(rel, forknum, blkno,
-												 RBM_NORMAL, NULL);
+												 RBM_NORMAL, NULL, NULL);
 
 			LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
 
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 15efb02bad..b8edae1fdc 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -771,6 +771,10 @@ CREATE VIEW pg_statio_all_tables AS
             pg_stat_get_blocks_hit(C.oid) AS heap_blks_hit,
             I.idx_blks_read AS idx_blks_read,
             I.idx_blks_hit AS idx_blks_hit,
+            I.idx_metadata_blks_read AS idx_metadata_blks_read,
+            I.idx_metadata_blks_hit AS idx_metadata_blks_hit,
+            I.idx_record_blks_read AS idx_record_blks_read,
+            I.idx_record_blks_hit AS idx_record_blks_hit,
             pg_stat_get_blocks_fetched(T.oid) -
                     pg_stat_get_blocks_hit(T.oid) AS toast_blks_read,
             pg_stat_get_blocks_hit(T.oid) AS toast_blks_hit,
@@ -784,7 +788,17 @@ CREATE VIEW pg_statio_all_tables AS
                          pg_stat_get_blocks_hit(indexrelid))::bigint
                      AS idx_blks_read,
                      sum(pg_stat_get_blocks_hit(indexrelid))::bigint
-                     AS idx_blks_hit
+                     AS idx_blks_hit,
+                     sum(pg_stat_get_metadata_blocks_fetched(indexrelid) -
+                         pg_stat_get_metadata_blocks_hit(indexrelid))::bigint
+                     AS idx_metadata_blks_read,
+                     sum(pg_stat_get_metadata_blocks_hit(indexrelid))::bigint
+                     AS idx_metadata_blks_hit,
+                     sum(pg_stat_get_record_blocks_fetched(indexrelid) -
+                         pg_stat_get_record_blocks_hit(indexrelid))::bigint
+                     AS idx_record_blks_read,
+                     sum(pg_stat_get_record_blocks_hit(indexrelid))::bigint
+                     AS idx_record_blks_hit
               FROM pg_index WHERE indrelid = C.oid ) I ON true
             LEFT JOIN LATERAL (
               SELECT sum(pg_stat_get_blocks_fetched(indexrelid) -
@@ -841,7 +855,13 @@ CREATE VIEW pg_statio_all_indexes AS
             I.relname AS indexrelname,
             pg_stat_get_blocks_fetched(I.oid) -
                     pg_stat_get_blocks_hit(I.oid) AS idx_blks_read,
-            pg_stat_get_blocks_hit(I.oid) AS idx_blks_hit
+            pg_stat_get_blocks_hit(I.oid) AS idx_blks_hit,
+            pg_stat_get_metadata_blocks_fetched(I.oid) -
+                    pg_stat_get_metadata_blocks_hit(I.oid) AS idx_metadata_blks_read,
+            pg_stat_get_metadata_blocks_hit(I.oid) AS idx_metadata_blks_hit,
+            pg_stat_get_record_blocks_fetched(I.oid) -
+                    pg_stat_get_record_blocks_hit(I.oid) AS idx_record_blks_read,
+            pg_stat_get_record_blocks_hit(I.oid) AS idx_record_blks_hit
     FROM pg_class C JOIN
             pg_index X ON C.oid = X.indrelid JOIN
             pg_class I ON I.oid = X.indexrelid
@@ -1076,6 +1096,12 @@ CREATE VIEW pg_stat_database AS
             pg_stat_get_db_blocks_fetched(D.oid) -
                     pg_stat_get_db_blocks_hit(D.oid) AS blks_read,
             pg_stat_get_db_blocks_hit(D.oid) AS blks_hit,
+            pg_stat_get_db_metadata_blocks_fetched(D.oid) -
+                    pg_stat_get_db_metadata_blocks_hit(D.oid) AS metadata_blks_read,
+            pg_stat_get_db_metadata_blocks_hit(D.oid) AS metadata_blks_hit,
+            pg_stat_get_db_record_blocks_fetched(D.oid) -
+                    pg_stat_get_db_record_blocks_hit(D.oid) AS record_blks_read,
+            pg_stat_get_db_record_blocks_hit(D.oid) AS record_blks_hit,
             pg_stat_get_db_tuples_returned(D.oid) AS tup_returned,
             pg_stat_get_db_tuples_fetched(D.oid) AS tup_fetched,
             pg_stat_get_db_tuples_inserted(D.oid) AS tup_inserted,
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 451ae6f7f6..13937d0dfe 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -1194,7 +1194,7 @@ read_seq_tuple(Relation rel, Buffer *buf, HeapTuple seqdatatuple)
 	sequence_magic *sm;
 	Form_pg_sequence_data seq;
 
-	*buf = ReadBuffer(rel, 0);
+	*buf = ReadBuffer(rel, 0, NULL);
 	LockBuffer(*buf, BUFFER_LOCK_EXCLUSIVE);
 
 	page = BufferGetPage(*buf);
diff --git a/src/backend/storage/aio/read_stream.c b/src/backend/storage/aio/read_stream.c
index 0e7f5557f5..7c371ec776 100644
--- a/src/backend/storage/aio/read_stream.c
+++ b/src/backend/storage/aio/read_stream.c
@@ -336,7 +336,8 @@ read_stream_start_pending_read(ReadStream *stream)
 								 &stream->buffers[buffer_index],
 								 stream->pending_read_blocknum,
 								 &nblocks,
-								 flags);
+								 flags,
+								 NULL);
 	stream->pinned_buffers += nblocks;
 
 	/* Remember whether we need to wait before returning this buffer. */
@@ -825,7 +826,8 @@ read_stream_next_buffer(ReadStream *stream, void **per_buffer_data)
 			if (likely(!StartReadBuffer(&stream->ios[0].op,
 										&stream->buffers[oldest_buffer_index],
 										next_blocknum,
-										flags)))
+										flags,
+										NULL)))
 			{
 				/* Fast return. */
 				return buffer;
diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index 1f2a9fe997..2bad31af5a 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -495,7 +495,8 @@ ForgetPrivateRefCountEntry(PrivateRefCountEntry *ref)
 static Buffer ReadBuffer_common(Relation rel,
 								SMgrRelation smgr, char smgr_persistence,
 								ForkNumber forkNum, BlockNumber blockNum,
-								ReadBufferMode mode, BufferAccessStrategy strategy);
+								ReadBufferMode mode, BufferAccessStrategy strategy,
+								bool *hit);
 static BlockNumber ExtendBufferedRelCommon(BufferManagerRelation bmr,
 										   ForkNumber fork,
 										   BufferAccessStrategy strategy,
@@ -755,9 +756,10 @@ ReadRecentBuffer(RelFileLocator rlocator, ForkNumber forkNum, BlockNumber blockN
  *		fork with RBM_NORMAL mode and default strategy.
  */
 Buffer
-ReadBuffer(Relation reln, BlockNumber blockNum)
+ReadBuffer(Relation reln, BlockNumber blockNum, bool *hit)
 {
-	return ReadBufferExtended(reln, MAIN_FORKNUM, blockNum, RBM_NORMAL, NULL);
+	return ReadBufferExtended(reln, MAIN_FORKNUM, blockNum, RBM_NORMAL, NULL,
+							  hit);
 }
 
 /*
@@ -803,7 +805,8 @@ ReadBuffer(Relation reln, BlockNumber blockNum)
  */
 inline Buffer
 ReadBufferExtended(Relation reln, ForkNumber forkNum, BlockNumber blockNum,
-				   ReadBufferMode mode, BufferAccessStrategy strategy)
+				   ReadBufferMode mode, BufferAccessStrategy strategy,
+				   bool *hit)
 {
 	Buffer		buf;
 
@@ -822,7 +825,7 @@ ReadBufferExtended(Relation reln, ForkNumber forkNum, BlockNumber blockNum,
 	 * miss.
 	 */
 	buf = ReadBuffer_common(reln, RelationGetSmgr(reln), 0,
-							forkNum, blockNum, mode, strategy);
+							forkNum, blockNum, mode, strategy, hit);
 
 	return buf;
 }
@@ -848,7 +851,7 @@ ReadBufferWithoutRelcache(RelFileLocator rlocator, ForkNumber forkNum,
 	return ReadBuffer_common(NULL, smgr,
 							 permanent ? RELPERSISTENCE_PERMANENT : RELPERSISTENCE_UNLOGGED,
 							 forkNum, blockNum,
-							 mode, strategy);
+							 mode, strategy, NULL);
 }
 
 /*
@@ -1016,7 +1019,7 @@ ExtendBufferedRelTo(BufferManagerRelation bmr,
 	{
 		Assert(extended_by == 0);
 		buffer = ReadBuffer_common(bmr.rel, bmr.smgr, bmr.relpersistence,
-								   fork, extend_to - 1, mode, strategy);
+								   fork, extend_to - 1, mode, strategy, NULL);
 	}
 
 	return buffer;
@@ -1113,7 +1116,8 @@ PinBufferForBlock(Relation rel,
 				  ForkNumber forkNum,
 				  BlockNumber blockNum,
 				  BufferAccessStrategy strategy,
-				  bool *foundPtr)
+				  bool *foundPtr,
+				  bool *hit)
 {
 	BufferDesc *bufHdr;
 	IOContext	io_context;
@@ -1164,8 +1168,11 @@ PinBufferForBlock(Relation rel,
 		 * zeroed instead), the per-relation stats always count them.
 		 */
 		pgstat_count_buffer_read(rel);
-		if (*foundPtr)
+		if (*foundPtr) {
+			if (hit)
+				*hit = true;
 			pgstat_count_buffer_hit(rel);
+		}
 	}
 	if (*foundPtr)
 	{
@@ -1193,7 +1200,8 @@ static pg_attribute_always_inline Buffer
 ReadBuffer_common(Relation rel, SMgrRelation smgr, char smgr_persistence,
 				  ForkNumber forkNum,
 				  BlockNumber blockNum, ReadBufferMode mode,
-				  BufferAccessStrategy strategy)
+				  BufferAccessStrategy strategy,
+				  bool *hit)
 {
 	ReadBuffersOperation operation;
 	Buffer		buffer;
@@ -1231,7 +1239,7 @@ ReadBuffer_common(Relation rel, SMgrRelation smgr, char smgr_persistence,
 		bool		found;
 
 		buffer = PinBufferForBlock(rel, smgr, persistence,
-								   forkNum, blockNum, strategy, &found);
+								   forkNum, blockNum, strategy, &found, hit);
 		ZeroAndLockBuffer(buffer, mode, found);
 		return buffer;
 	}
@@ -1252,7 +1260,8 @@ ReadBuffer_common(Relation rel, SMgrRelation smgr, char smgr_persistence,
 	if (StartReadBuffer(&operation,
 						&buffer,
 						blockNum,
-						flags))
+						flags,
+						hit))
 		WaitReadBuffers(&operation);
 
 	return buffer;
@@ -1264,7 +1273,8 @@ StartReadBuffersImpl(ReadBuffersOperation *operation,
 					 BlockNumber blockNum,
 					 int *nblocks,
 					 int flags,
-					 bool allow_forwarding)
+					 bool allow_forwarding,
+					 bool *hit)
 {
 	int			actual_nblocks = *nblocks;
 	int			maxcombine = 0;
@@ -1322,7 +1332,8 @@ StartReadBuffersImpl(ReadBuffersOperation *operation,
 										   operation->forknum,
 										   blockNum + i,
 										   operation->strategy,
-										   &found);
+										   &found,
+										   hit);
 		}
 
 		if (found)
@@ -1495,10 +1506,11 @@ StartReadBuffers(ReadBuffersOperation *operation,
 				 Buffer *buffers,
 				 BlockNumber blockNum,
 				 int *nblocks,
-				 int flags)
+				 int flags,
+				 bool *hit)
 {
 	return StartReadBuffersImpl(operation, buffers, blockNum, nblocks, flags,
-								true /* expect forwarded buffers */ );
+								true /* expect forwarded buffers */, hit);
 }
 
 /*
@@ -1513,13 +1525,14 @@ bool
 StartReadBuffer(ReadBuffersOperation *operation,
 				Buffer *buffer,
 				BlockNumber blocknum,
-				int flags)
+				int flags,
+				bool *hit)
 {
 	int			nblocks = 1;
 	bool		result;
 
 	result = StartReadBuffersImpl(operation, buffer, blocknum, &nblocks, flags,
-								  false /* single block, no forwarding */ );
+								  false /* single block, no forwarding */, hit);
 	Assert(nblocks == 1);		/* single block can't be short */
 
 	return result;
@@ -3013,7 +3026,8 @@ MarkBufferDirty(Buffer buffer)
 Buffer
 ReleaseAndReadBuffer(Buffer buffer,
 					 Relation relation,
-					 BlockNumber blockNum)
+					 BlockNumber blockNum,
+					 bool *hit)
 {
 	ForkNumber	forkNum = MAIN_FORKNUM;
 	BufferDesc *bufHdr;
@@ -3042,7 +3056,7 @@ ReleaseAndReadBuffer(Buffer buffer,
 		}
 	}
 
-	return ReadBuffer(relation, blockNum);
+	return ReadBuffer(relation, blockNum, hit);
 }
 
 /*
diff --git a/src/backend/storage/freespace/freespace.c b/src/backend/storage/freespace/freespace.c
index 4773a9cc65..f9f6259ad4 100644
--- a/src/backend/storage/freespace/freespace.c
+++ b/src/backend/storage/freespace/freespace.c
@@ -593,7 +593,7 @@ fsm_readbuf(Relation rel, FSMAddress addr, bool extend)
 			return InvalidBuffer;
 	}
 	else
-		buf = ReadBufferExtended(rel, FSM_FORKNUM, blkno, RBM_ZERO_ON_ERROR, NULL);
+		buf = ReadBufferExtended(rel, FSM_FORKNUM, blkno, RBM_ZERO_ON_ERROR, NULL, NULL);
 
 	/*
 	 * Initializing the page when needed is trickier than it looks, because of
diff --git a/src/backend/utils/activity/pgstat_database.c b/src/backend/utils/activity/pgstat_database.c
index 52d82d2535..36913aea93 100644
--- a/src/backend/utils/activity/pgstat_database.c
+++ b/src/backend/utils/activity/pgstat_database.c
@@ -443,6 +443,10 @@ pgstat_database_flush_cb(PgStat_EntryRef *entry_ref, bool nowait)
 	PGSTAT_ACCUM_DBCOUNT(xact_rollback);
 	PGSTAT_ACCUM_DBCOUNT(blocks_fetched);
 	PGSTAT_ACCUM_DBCOUNT(blocks_hit);
+	PGSTAT_ACCUM_DBCOUNT(metadata_blocks_fetched);
+	PGSTAT_ACCUM_DBCOUNT(metadata_blocks_hit);
+	PGSTAT_ACCUM_DBCOUNT(record_blocks_fetched);
+	PGSTAT_ACCUM_DBCOUNT(record_blocks_hit);
 
 	PGSTAT_ACCUM_DBCOUNT(tuples_returned);
 	PGSTAT_ACCUM_DBCOUNT(tuples_fetched);
diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c
index eeb2d43cb1..ef8a40f4f0 100644
--- a/src/backend/utils/activity/pgstat_relation.c
+++ b/src/backend/utils/activity/pgstat_relation.c
@@ -880,6 +880,10 @@ pgstat_relation_flush_cb(PgStat_EntryRef *entry_ref, bool nowait)
 
 	tabentry->blocks_fetched += lstats->counts.blocks_fetched;
 	tabentry->blocks_hit += lstats->counts.blocks_hit;
+	tabentry->metadata_blocks_fetched += lstats->counts.metadata_blocks_fetched;
+	tabentry->metadata_blocks_hit += lstats->counts.metadata_blocks_hit;
+	tabentry->record_blocks_fetched += lstats->counts.record_blocks_fetched;
+	tabentry->record_blocks_hit += lstats->counts.record_blocks_hit;
 
 	/* Clamp live_tuples in case of negative delta_live_tuples */
 	tabentry->live_tuples = Max(tabentry->live_tuples, 0);
@@ -897,6 +901,10 @@ pgstat_relation_flush_cb(PgStat_EntryRef *entry_ref, bool nowait)
 	dbentry->tuples_deleted += lstats->counts.tuples_deleted;
 	dbentry->blocks_fetched += lstats->counts.blocks_fetched;
 	dbentry->blocks_hit += lstats->counts.blocks_hit;
+	dbentry->metadata_blocks_fetched += lstats->counts.metadata_blocks_fetched;
+	dbentry->metadata_blocks_hit += lstats->counts.metadata_blocks_hit;
+	dbentry->record_blocks_fetched += lstats->counts.record_blocks_fetched;
+	dbentry->record_blocks_hit += lstats->counts.record_blocks_hit;
 
 	return true;
 }
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 97af7c6554..4b8aab1465 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -67,6 +67,18 @@ PG_STAT_GET_RELENTRY_INT64(blocks_fetched)
 /* pg_stat_get_blocks_hit */
 PG_STAT_GET_RELENTRY_INT64(blocks_hit)
 
+/* pg_stat_get_metadata_blocks_fetched */
+PG_STAT_GET_RELENTRY_INT64(metadata_blocks_fetched)
+
+/* pg_stat_get_metadata_blocks_hit */
+PG_STAT_GET_RELENTRY_INT64(metadata_blocks_hit)
+
+/* pg_stat_get_record_blocks_fetched */
+PG_STAT_GET_RELENTRY_INT64(record_blocks_fetched)
+
+/* pg_stat_get_record_blocks_hit */
+PG_STAT_GET_RELENTRY_INT64(record_blocks_hit)
+
 /* pg_stat_get_dead_tuples */
 PG_STAT_GET_RELENTRY_INT64(dead_tuples)
 
@@ -1034,6 +1046,18 @@ PG_STAT_GET_DBENTRY_INT64(blocks_fetched)
 /* pg_stat_get_db_blocks_hit */
 PG_STAT_GET_DBENTRY_INT64(blocks_hit)
 
+/* pg_stat_get_db_metadata_blocks_fetched */
+PG_STAT_GET_DBENTRY_INT64(metadata_blocks_fetched)
+
+/* pg_stat_get_db_metadata_blocks_hit */
+PG_STAT_GET_DBENTRY_INT64(metadata_blocks_hit)
+
+/* pg_stat_get_db_record_blocks_fetched */
+PG_STAT_GET_DBENTRY_INT64(record_blocks_fetched)
+
+/* pg_stat_get_db_record_blocks_hit */
+PG_STAT_GET_DBENTRY_INT64(record_blocks_hit)
+
 /* pg_stat_get_db_conflict_bufferpin */
 PG_STAT_GET_DBENTRY_INT64(conflict_bufferpin)
 
diff --git a/src/include/access/nbtree.h b/src/include/access/nbtree.h
index ebca02588d..9e1effdf2d 100644
--- a/src/include/access/nbtree.h
+++ b/src/include/access/nbtree.h
@@ -1271,10 +1271,10 @@ extern int	_bt_getrootheight(Relation rel);
 extern void _bt_metaversion(Relation rel, bool *heapkeyspace,
 							bool *allequalimage);
 extern void _bt_checkpage(Relation rel, Buffer buf);
-extern Buffer _bt_getbuf(Relation rel, BlockNumber blkno, int access);
+extern Buffer _bt_getbuf(Relation rel, BlockNumber blkno, int access, bool *hit);
 extern Buffer _bt_allocbuf(Relation rel, Relation heaprel);
 extern Buffer _bt_relandgetbuf(Relation rel, Buffer obuf,
-							   BlockNumber blkno, int access);
+							   BlockNumber blkno, int access, bool *hit);
 extern void _bt_relbuf(Relation rel, Buffer buf);
 extern void _bt_lockbuf(Relation rel, Buffer buf, int access);
 extern void _bt_unlockbuf(Relation rel, Buffer buf);
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 62beb71da2..3fbcc71eb4 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5579,6 +5579,22 @@
   proname => 'pg_stat_get_blocks_hit', provolatile => 's', proparallel => 'r',
   prorettype => 'int8', proargtypes => 'oid',
   prosrc => 'pg_stat_get_blocks_hit' },
+{ oid => '8888', descr => 'statistics: number of record blocks fetched',
+  proname => 'pg_stat_get_record_blocks_fetched', provolatile => 's',
+  proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
+  prosrc => 'pg_stat_get_record_blocks_fetched' },
+{ oid => '8889', descr => 'statistics: number of record blocks found in cache',
+  proname => 'pg_stat_get_record_blocks_hit', provolatile => 's', proparallel => 'r',
+  prorettype => 'int8', proargtypes => 'oid',
+  prosrc => 'pg_stat_get_record_blocks_hit' },
+{ oid => '8890', descr => 'statistics: number of metadata blocks fetched',
+  proname => 'pg_stat_get_metadata_blocks_fetched', provolatile => 's',
+  proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
+  prosrc => 'pg_stat_get_metadata_blocks_fetched' },
+{ oid => '8891', descr => 'statistics: number of metadata blocks found in cache',
+  proname => 'pg_stat_get_metadata_blocks_hit', provolatile => 's', proparallel => 'r',
+  prorettype => 'int8', proargtypes => 'oid',
+  prosrc => 'pg_stat_get_metadata_blocks_hit' },
 { oid => '2781', descr => 'statistics: last manual vacuum time for a table',
   proname => 'pg_stat_get_last_vacuum_time', provolatile => 's',
   proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid',
@@ -5779,6 +5795,22 @@
   proname => 'pg_stat_get_db_blocks_hit', provolatile => 's',
   proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
   prosrc => 'pg_stat_get_db_blocks_hit' },
+{ oid => '8892', descr => 'statistics: number of db record blocks fetched',
+  proname => 'pg_stat_get_db_record_blocks_fetched', provolatile => 's',
+  proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
+  prosrc => 'pg_stat_get_db_record_blocks_fetched' },
+{ oid => '8893', descr => 'statistics: blocks found in cache for database',
+  proname => 'pg_stat_get_db_record_blocks_hit', provolatile => 's',
+  proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
+  prosrc => 'pg_stat_get_db_record_blocks_hit' },
+{ oid => '8894', descr => 'statistics: number of metadata blocks fetched',
+  proname => 'pg_stat_get_db_metadata_blocks_fetched', provolatile => 's',
+  proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
+  prosrc => 'pg_stat_get_db_metadata_blocks_fetched' },
+{ oid => '8895', descr => 'statistics: number of metadata blocks found in cache',
+  proname => 'pg_stat_get_db_metadata_blocks_hit', provolatile => 's', proparallel => 'r',
+  prorettype => 'int8', proargtypes => 'oid',
+  prosrc => 'pg_stat_get_db_metadata_blocks_hit' },
 { oid => '2758', descr => 'statistics: tuples returned for database',
   proname => 'pg_stat_get_db_tuples_returned', provolatile => 's',
   proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
diff --git a/src/include/pgstat.h b/src/include/pgstat.h
index 378f2f2c2b..28ed6dc471 100644
--- a/src/include/pgstat.h
+++ b/src/include/pgstat.h
@@ -153,6 +153,11 @@ typedef struct PgStat_TableCounts
 
 	PgStat_Counter blocks_fetched;
 	PgStat_Counter blocks_hit;
+
+	PgStat_Counter metadata_blocks_fetched;
+	PgStat_Counter metadata_blocks_hit;
+	PgStat_Counter record_blocks_fetched;
+	PgStat_Counter record_blocks_hit;
 } PgStat_TableCounts;
 
 /* ----------
@@ -345,6 +350,10 @@ typedef struct PgStat_StatDBEntry
 	PgStat_Counter xact_rollback;
 	PgStat_Counter blocks_fetched;
 	PgStat_Counter blocks_hit;
+	PgStat_Counter metadata_blocks_fetched;
+	PgStat_Counter metadata_blocks_hit;
+	PgStat_Counter record_blocks_fetched;
+	PgStat_Counter record_blocks_hit;
 	PgStat_Counter tuples_returned;
 	PgStat_Counter tuples_fetched;
 	PgStat_Counter tuples_inserted;
@@ -440,6 +449,11 @@ typedef struct PgStat_StatTabEntry
 	PgStat_Counter blocks_fetched;
 	PgStat_Counter blocks_hit;
 
+	PgStat_Counter metadata_blocks_fetched;
+	PgStat_Counter metadata_blocks_hit;
+	PgStat_Counter record_blocks_fetched;
+	PgStat_Counter record_blocks_hit;
+
 	TimestampTz last_vacuum_time;	/* user initiated vacuum */
 	PgStat_Counter vacuum_count;
 	TimestampTz last_autovacuum_time;	/* autovacuum initiated */
@@ -711,6 +725,35 @@ extern void pgstat_report_analyze(Relation rel,
 		if (pgstat_should_count_relation(rel))						\
 			(rel)->pgstat_info->counts.blocks_hit++;				\
 	} while (0)
+#define pgstat_count_metadata_index_buffer(rel, hit)			     \
+	do {															 \
+		if (pgstat_should_count_relation(rel)) {                     \
+			(rel)->pgstat_info->counts.metadata_blocks_fetched++;    \
+			(rel)->pgstat_info->counts.metadata_blocks_hit += (hit); \
+		}       												     \
+	} while (0)
+#define pgstat_count_record_index_buffer(rel, hit)					\
+	do {															\
+		if (pgstat_should_count_relation(rel)) {                    \
+			(rel)->pgstat_info->counts.record_blocks_fetched++;     \
+			(rel)->pgstat_info->counts.record_blocks_hit += (hit);	\
+		}														 	\
+	} while (0)
+#define pgstat_count_index_buffer(rel, metadata, hit)			    \
+	do {															\
+		if (pgstat_should_count_relation(rel)) {                    \
+			if ((metadata)) { 										\
+				(rel)->pgstat_info->counts.metadata_blocks_fetched++;\
+				if ((hit))                                          \
+					(rel)->pgstat_info->counts.metadata_blocks_hit++;\
+			}														\
+			else {  												\
+				(rel)->pgstat_info->counts.record_blocks_fetched++; \
+				if ((hit)) 										    \
+				   (rel)->pgstat_info->counts.record_blocks_hit++;  \
+			}														\
+	   }															\
+	} while (0)
 
 extern void pgstat_count_heap_insert(Relation rel, PgStat_Counter n);
 extern void pgstat_count_heap_update(Relation rel, bool hot, bool newpage);
diff --git a/src/include/storage/bufmgr.h b/src/include/storage/bufmgr.h
index 41fdc1e769..aa079fde48 100644
--- a/src/include/storage/bufmgr.h
+++ b/src/include/storage/bufmgr.h
@@ -208,10 +208,10 @@ extern PrefetchBufferResult PrefetchBuffer(Relation reln, ForkNumber forkNum,
 										   BlockNumber blockNum);
 extern bool ReadRecentBuffer(RelFileLocator rlocator, ForkNumber forkNum,
 							 BlockNumber blockNum, Buffer recent_buffer);
-extern Buffer ReadBuffer(Relation reln, BlockNumber blockNum);
+extern Buffer ReadBuffer(Relation reln, BlockNumber blockNum, bool *hit);
 extern Buffer ReadBufferExtended(Relation reln, ForkNumber forkNum,
 								 BlockNumber blockNum, ReadBufferMode mode,
-								 BufferAccessStrategy strategy);
+								 BufferAccessStrategy strategy, bool *hit);
 extern Buffer ReadBufferWithoutRelcache(RelFileLocator rlocator,
 										ForkNumber forkNum, BlockNumber blockNum,
 										ReadBufferMode mode, BufferAccessStrategy strategy,
@@ -220,12 +220,14 @@ extern Buffer ReadBufferWithoutRelcache(RelFileLocator rlocator,
 extern bool StartReadBuffer(ReadBuffersOperation *operation,
 							Buffer *buffer,
 							BlockNumber blocknum,
-							int flags);
+							int flags,
+							bool *hit);
 extern bool StartReadBuffers(ReadBuffersOperation *operation,
 							 Buffer *buffers,
 							 BlockNumber blockNum,
 							 int *nblocks,
-							 int flags);
+							 int flags,
+							 bool *hit);
 extern void WaitReadBuffers(ReadBuffersOperation *operation);
 
 extern void ReleaseBuffer(Buffer buffer);
@@ -236,7 +238,7 @@ extern void MarkBufferDirty(Buffer buffer);
 extern void IncrBufferRefCount(Buffer buffer);
 extern void CheckBufferIsPinnedOnce(Buffer buffer);
 extern Buffer ReleaseAndReadBuffer(Buffer buffer, Relation relation,
-								   BlockNumber blockNum);
+								   BlockNumber blockNum, bool *hit);
 
 extern Buffer ExtendBufferedRel(BufferManagerRelation bmr,
 								ForkNumber forkNum,
diff --git a/src/test/modules/test_aio/test_aio.c b/src/test/modules/test_aio/test_aio.c
index 1d776010ef..dbcb9d0f4c 100644
--- a/src/test/modules/test_aio/test_aio.c
+++ b/src/test/modules/test_aio/test_aio.c
@@ -211,7 +211,7 @@ modify_rel_block(PG_FUNCTION_ARGS)
 	rel = relation_open(relid, AccessExclusiveLock);
 
 	buf = ReadBufferExtended(rel, MAIN_FORKNUM, blkno,
-							 RBM_ZERO_ON_ERROR, NULL);
+							 RBM_ZERO_ON_ERROR, NULL, NULL);
 
 	LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
 
@@ -312,7 +312,7 @@ create_toy_buffer(Relation rel, BlockNumber blkno)
 	bool		was_pinned = false;
 
 	/* place buffer in shared buffers without erroring out */
-	buf = ReadBufferExtended(rel, MAIN_FORKNUM, blkno, RBM_ZERO_AND_LOCK, NULL);
+	buf = ReadBufferExtended(rel, MAIN_FORKNUM, blkno, RBM_ZERO_AND_LOCK, NULL, NULL);
 	LockBuffer(buf, BUFFER_LOCK_UNLOCK);
 
 	if (RelationUsesLocalBuffers(rel))
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 6cf828ca8d..62d1ec90f2 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1868,6 +1868,10 @@ pg_stat_database| SELECT oid AS datid,
     pg_stat_get_db_xact_rollback(oid) AS xact_rollback,
     (pg_stat_get_db_blocks_fetched(oid) - pg_stat_get_db_blocks_hit(oid)) AS blks_read,
     pg_stat_get_db_blocks_hit(oid) AS blks_hit,
+    (pg_stat_get_db_metadata_blocks_fetched(oid) - pg_stat_get_db_metadata_blocks_hit(oid)) AS metadata_blks_read,
+    pg_stat_get_db_metadata_blocks_hit(oid) AS metadata_blks_hit,
+    (pg_stat_get_db_record_blocks_fetched(oid) - pg_stat_get_db_record_blocks_hit(oid)) AS record_blks_read,
+    pg_stat_get_db_record_blocks_hit(oid) AS record_blks_hit,
     pg_stat_get_db_tuples_returned(oid) AS tup_returned,
     pg_stat_get_db_tuples_fetched(oid) AS tup_fetched,
     pg_stat_get_db_tuples_inserted(oid) AS tup_inserted,
@@ -2360,7 +2364,11 @@ pg_statio_all_indexes| SELECT c.oid AS relid,
     c.relname,
     i.relname AS indexrelname,
     (pg_stat_get_blocks_fetched(i.oid) - pg_stat_get_blocks_hit(i.oid)) AS idx_blks_read,
-    pg_stat_get_blocks_hit(i.oid) AS idx_blks_hit
+    pg_stat_get_blocks_hit(i.oid) AS idx_blks_hit,
+    (pg_stat_get_metadata_blocks_fetched(i.oid) - pg_stat_get_metadata_blocks_hit(i.oid)) AS idx_metadata_blks_read,
+    pg_stat_get_metadata_blocks_hit(i.oid) AS idx_metadata_blks_hit,
+    (pg_stat_get_record_blocks_fetched(i.oid) - pg_stat_get_record_blocks_hit(i.oid)) AS idx_record_blks_read,
+    pg_stat_get_record_blocks_hit(i.oid) AS idx_record_blks_hit
    FROM (((pg_class c
      JOIN pg_index x ON ((c.oid = x.indrelid)))
      JOIN pg_class i ON ((i.oid = x.indexrelid)))
@@ -2381,6 +2389,10 @@ pg_statio_all_tables| SELECT c.oid AS relid,
     pg_stat_get_blocks_hit(c.oid) AS heap_blks_hit,
     i.idx_blks_read,
     i.idx_blks_hit,
+    i.idx_metadata_blks_read,
+    i.idx_metadata_blks_hit,
+    i.idx_record_blks_read,
+    i.idx_record_blks_hit,
     (pg_stat_get_blocks_fetched(t.oid) - pg_stat_get_blocks_hit(t.oid)) AS toast_blks_read,
     pg_stat_get_blocks_hit(t.oid) AS toast_blks_hit,
     x.idx_blks_read AS tidx_blks_read,
@@ -2389,7 +2401,11 @@ pg_statio_all_tables| SELECT c.oid AS relid,
      LEFT JOIN pg_class t ON ((c.reltoastrelid = t.oid)))
      LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
      LEFT JOIN LATERAL ( SELECT (sum((pg_stat_get_blocks_fetched(pg_index.indexrelid) - pg_stat_get_blocks_hit(pg_index.indexrelid))))::bigint AS idx_blks_read,
-            (sum(pg_stat_get_blocks_hit(pg_index.indexrelid)))::bigint AS idx_blks_hit
+            (sum(pg_stat_get_blocks_hit(pg_index.indexrelid)))::bigint AS idx_blks_hit,
+            (sum((pg_stat_get_metadata_blocks_fetched(pg_index.indexrelid) - pg_stat_get_metadata_blocks_hit(pg_index.indexrelid))))::bigint AS idx_metadata_blks_read,
+            (sum(pg_stat_get_metadata_blocks_hit(pg_index.indexrelid)))::bigint AS idx_metadata_blks_hit,
+            (sum((pg_stat_get_record_blocks_fetched(pg_index.indexrelid) - pg_stat_get_record_blocks_hit(pg_index.indexrelid))))::bigint AS idx_record_blks_read,
+            (sum(pg_stat_get_record_blocks_hit(pg_index.indexrelid)))::bigint AS idx_record_blks_hit
            FROM pg_index
           WHERE (pg_index.indrelid = c.oid)) i ON (true))
      LEFT JOIN LATERAL ( SELECT (sum((pg_stat_get_blocks_fetched(pg_index.indexrelid) - pg_stat_get_blocks_hit(pg_index.indexrelid))))::bigint AS idx_blks_read,
@@ -2403,7 +2419,11 @@ pg_statio_sys_indexes| SELECT relid,
     relname,
     indexrelname,
     idx_blks_read,
-    idx_blks_hit
+    idx_blks_hit,
+    idx_metadata_blks_read,
+    idx_metadata_blks_hit,
+    idx_record_blks_read,
+    idx_record_blks_hit
    FROM pg_statio_all_indexes
   WHERE ((schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (schemaname ~ '^pg_toast'::text));
 pg_statio_sys_sequences| SELECT relid,
@@ -2420,6 +2440,10 @@ pg_statio_sys_tables| SELECT relid,
     heap_blks_hit,
     idx_blks_read,
     idx_blks_hit,
+    idx_metadata_blks_read,
+    idx_metadata_blks_hit,
+    idx_record_blks_read,
+    idx_record_blks_hit,
     toast_blks_read,
     toast_blks_hit,
     tidx_blks_read,
@@ -2432,7 +2456,11 @@ pg_statio_user_indexes| SELECT relid,
     relname,
     indexrelname,
     idx_blks_read,
-    idx_blks_hit
+    idx_blks_hit,
+    idx_metadata_blks_read,
+    idx_metadata_blks_hit,
+    idx_record_blks_read,
+    idx_record_blks_hit
    FROM pg_statio_all_indexes
   WHERE ((schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (schemaname !~ '^pg_toast'::text));
 pg_statio_user_sequences| SELECT relid,
@@ -2449,6 +2477,10 @@ pg_statio_user_tables| SELECT relid,
     heap_blks_hit,
     idx_blks_read,
     idx_blks_hit,
+    idx_metadata_blks_read,
+    idx_metadata_blks_hit,
+    idx_record_blks_read,
+    idx_record_blks_hit,
     toast_blks_read,
     toast_blks_hit,
     tidx_blks_read,
diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out
index 776f1ad0e5..da41526f16 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -1868,4 +1868,148 @@ SELECT * FROM check_estimated_rows('SELECT * FROM table_fillfactor');
 (1 row)
 
 DROP TABLE table_fillfactor;
+-- brin indexes: test stats collection for metadata and record index block
+-- hits and reads adding up to idx_blks_read and idx_blks_hit respectively
+SELECT count(*)
+  FROM brin_test
+ WHERE a = 19 AND b = 89;
+ count 
+-------
+     1
+(1 row)
+
+-- ensure pending stats are flushed
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+-- check effects
+BEGIN;
+SET LOCAL stats_fetch_consistency = snapshot;
+SELECT idx_metadata_blks_hit + idx_record_blks_hit = idx_blks_hit,
+       idx_metadata_blks_read + idx_record_blks_read = idx_blks_read
+  FROM pg_statio_all_indexes
+ WHERE indexrelname='brin_test_a_idx';
+ ?column? | ?column? 
+----------+----------
+ t        | t
+(1 row)
+
+COMMIT;
+-- gist indexes: test stats collection for metadata and record index block
+-- hits and reads adding up to idx_blks_read and idx_blks_hit respectively
+select count(*) from gist_point_tbl where p <@ box(point(0,0), point(200, 200));
+ count 
+-------
+    10
+(1 row)
+
+-- ensure pending stats are flushed
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+-- check effects
+BEGIN;
+SET LOCAL stats_fetch_consistency = snapshot;
+SELECT idx_metadata_blks_hit + idx_record_blks_hit = idx_blks_hit,
+       idx_metadata_blks_read + idx_record_blks_read = idx_blks_read
+  FROM pg_statio_all_indexes
+ WHERE indexrelname='gist_pointidx';
+ ?column? | ?column? 
+----------+----------
+ t        | t
+(1 row)
+
+COMMIT;
+-- hash indexes: test stats collection for metadata and record index block
+-- hits and reads adding up to idx_blks_read and idx_blks_hit respectively
+SELECT count(*)
+  FROM hash_name_heap
+ WHERE random = '1505703298';
+ count 
+-------
+     1
+(1 row)
+
+-- ensure pending stats are flushed
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+-- check effects
+BEGIN;
+SET LOCAL stats_fetch_consistency = snapshot;
+SELECT idx_metadata_blks_hit + idx_record_blks_hit = idx_blks_hit,
+       idx_metadata_blks_read + idx_record_blks_read = idx_blks_read
+  FROM pg_statio_all_indexes
+ WHERE indexrelname='hash_name_index';
+ ?column? | ?column? 
+----------+----------
+ t        | t
+(1 row)
+
+COMMIT;
+-- spgist indexes: test stats collection for metadata and record index block
+-- hits and reads adding up to idx_blks_read and idx_blks_hit respectively
+select count(*) from spgist_point_tbl where p <@ box(point(0,0), point(200, 200));
+ count 
+-------
+     9
+(1 row)
+
+-- ensure pending stats are flushed
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+-- check effects
+BEGIN;
+SET LOCAL stats_fetch_consistency = snapshot;
+SELECT idx_metadata_blks_hit + idx_record_blks_hit = idx_blks_hit,
+       idx_metadata_blks_read + idx_record_blks_read = idx_blks_read
+  FROM pg_statio_all_indexes
+ WHERE indexrelname='spgist_point_idx';
+ ?column? | ?column? 
+----------+----------
+ t        | t
+(1 row)
+
+COMMIT;
+-- b-tree indexes: test stats collection for metadata and record index block
+-- hits and reads adding up to idx_blks_read and idx_blks_hit respectively
+select count(*) from tenk2 where unique1 = '1504';
+ count 
+-------
+     1
+(1 row)
+
+-- ensure pending stats are flushed
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+-- check effects
+BEGIN;
+SET LOCAL stats_fetch_consistency = snapshot;
+SELECT idx_metadata_blks_hit + idx_record_blks_hit = idx_blks_hit,
+       idx_metadata_blks_read + idx_record_blks_read = idx_blks_read
+  FROM pg_statio_all_indexes
+ WHERE indexrelname='tenk2_unique1';
+ ?column? | ?column? 
+----------+----------
+ t        | t
+(1 row)
+
+COMMIT;
 -- End of Stats Test
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 232ab8db8f..7815b8d1c0 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -925,4 +925,98 @@ SELECT * FROM check_estimated_rows('SELECT * FROM table_fillfactor');
 
 DROP TABLE table_fillfactor;
 
+-- brin indexes: test stats collection for metadata and record index block
+-- hits and reads adding up to idx_blks_read and idx_blks_hit respectively
+SELECT count(*)
+  FROM brin_test
+ WHERE a = 19 AND b = 89;
+
+-- ensure pending stats are flushed
+SELECT pg_stat_force_next_flush();
+
+-- check effects
+BEGIN;
+SET LOCAL stats_fetch_consistency = snapshot;
+
+SELECT idx_metadata_blks_hit + idx_record_blks_hit = idx_blks_hit,
+       idx_metadata_blks_read + idx_record_blks_read = idx_blks_read
+  FROM pg_statio_all_indexes
+ WHERE indexrelname='brin_test_a_idx';
+
+COMMIT;
+
+-- gist indexes: test stats collection for metadata and record index block
+-- hits and reads adding up to idx_blks_read and idx_blks_hit respectively
+select count(*) from gist_point_tbl where p <@ box(point(0,0), point(200, 200));
+
+-- ensure pending stats are flushed
+SELECT pg_stat_force_next_flush();
+
+-- check effects
+BEGIN;
+SET LOCAL stats_fetch_consistency = snapshot;
+
+SELECT idx_metadata_blks_hit + idx_record_blks_hit = idx_blks_hit,
+       idx_metadata_blks_read + idx_record_blks_read = idx_blks_read
+  FROM pg_statio_all_indexes
+ WHERE indexrelname='gist_pointidx';
+
+COMMIT;
+
+-- hash indexes: test stats collection for metadata and record index block
+-- hits and reads adding up to idx_blks_read and idx_blks_hit respectively
+SELECT count(*)
+  FROM hash_name_heap
+ WHERE random = '1505703298';
+
+-- ensure pending stats are flushed
+SELECT pg_stat_force_next_flush();
+
+-- check effects
+BEGIN;
+SET LOCAL stats_fetch_consistency = snapshot;
+
+SELECT idx_metadata_blks_hit + idx_record_blks_hit = idx_blks_hit,
+       idx_metadata_blks_read + idx_record_blks_read = idx_blks_read
+  FROM pg_statio_all_indexes
+ WHERE indexrelname='hash_name_index';
+
+COMMIT;
+
+-- spgist indexes: test stats collection for metadata and record index block
+-- hits and reads adding up to idx_blks_read and idx_blks_hit respectively
+select count(*) from spgist_point_tbl where p <@ box(point(0,0), point(200, 200));
+
+-- ensure pending stats are flushed
+SELECT pg_stat_force_next_flush();
+
+-- check effects
+BEGIN;
+SET LOCAL stats_fetch_consistency = snapshot;
+
+SELECT idx_metadata_blks_hit + idx_record_blks_hit = idx_blks_hit,
+       idx_metadata_blks_read + idx_record_blks_read = idx_blks_read
+  FROM pg_statio_all_indexes
+ WHERE indexrelname='spgist_point_idx';
+
+COMMIT;
+
+-- b-tree indexes: test stats collection for metadata and record index block
+-- hits and reads adding up to idx_blks_read and idx_blks_hit respectively
+select count(*) from tenk2 where unique1 = '1504';
+
+-- ensure pending stats are flushed
+SELECT pg_stat_force_next_flush();
+
+-- check effects
+BEGIN;
+SET LOCAL stats_fetch_consistency = snapshot;
+
+SELECT idx_metadata_blks_hit + idx_record_blks_hit = idx_blks_hit,
+       idx_metadata_blks_read + idx_record_blks_read = idx_blks_read
+  FROM pg_statio_all_indexes
+ WHERE indexrelname='tenk2_unique1';
+
+COMMIT;
+
 -- End of Stats Test
-- 
2.39.5 (Apple Git-154)