v1-0001-Preliminary-work-to-capture-and-expose-separate-r.patch

text/plain

Filename: v1-0001-Preliminary-work-to-capture-and-expose-separate-r.patch
Type: text/plain
Part: 0
Message: Metadata and record block access stats for indexes
From a540a042ffb0b348254afdfdce39199900f9c7ec Mon Sep 17 00:00:00 2001
From: Mircea Cadariu <cadariu.mircea@gmail.com>
Date: Thu, 20 Feb 2025 13:45:12 +0000
Subject: [PATCH v1] Preliminary work to capture and expose separate record
 (leaf page) and metadata (non-leaf page) index access statistics in the
 system views, with partial coverage of B-Trees.

---
 contrib/amcheck/verify_heapam.c              |   2 +-
 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_prewarm/autoprewarm.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            |  10 +-
 contrib/pgstattuple/pgstattuple.c            |  10 +-
 doc/src/sgml/monitoring.sgml                 | 110 +++++++++++++++++++
 src/backend/access/brin/brin.c               |   4 +-
 src/backend/access/brin/brin_pageops.c       |   4 +-
 src/backend/access/brin/brin_revmap.c        |  12 +-
 src/backend/access/gin/ginbtree.c            |  11 +-
 src/backend/access/gin/ginfast.c             |  14 +--
 src/backend/access/gin/ginget.c              |   6 +-
 src/backend/access/gin/ginutil.c             |   6 +-
 src/backend/access/gin/ginvacuum.c           |  22 ++--
 src/backend/access/gist/gist.c               |  10 +-
 src/backend/access/gist/gistbuild.c          |  10 +-
 src/backend/access/gist/gistget.c            |   4 +-
 src/backend/access/gist/gistutil.c           |   2 +-
 src/backend/access/gist/gistvacuum.c         |   6 +-
 src/backend/access/hash/hash.c               |   3 +-
 src/backend/access/hash/hashpage.c           |  10 +-
 src/backend/access/heap/heapam.c             |  16 +--
 src/backend/access/heap/heapam_handler.c     |   9 +-
 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        |  32 ++++--
 src/backend/access/nbtree/nbtpage.c          |  65 +++++++----
 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      |   4 +-
 src/backend/access/spgist/spgscan.c          |   4 +-
 src/backend/access/spgist/spgutils.c         |   8 +-
 src/backend/access/spgist/spgvacuum.c        |   4 +-
 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          |  52 +++++----
 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                         |  45 ++++++++
 src/include/storage/bufmgr.h                 |  12 +-
 src/test/regress/expected/rules.out          |  40 ++++++-
 58 files changed, 557 insertions(+), 201 deletions(-)

diff --git a/contrib/amcheck/verify_heapam.c b/contrib/amcheck/verify_heapam.c
index 827312306f..5b611b15e8 100644
--- a/contrib/amcheck/verify_heapam.c
+++ b/contrib/amcheck/verify_heapam.c
@@ -439,7 +439,7 @@ verify_heapam(PG_FUNCTION_ARGS)
 
 		/* Read and lock the next page. */
 		ctx.buffer = ReadBufferExtended(ctx.rel, MAIN_FORKNUM, ctx.blkno,
-										RBM_NORMAL, ctx.bstrategy);
+										RBM_NORMAL, ctx.bstrategy, NULL);
 		LockBuffer(ctx.buffer, BUFFER_LOCK_SHARE);
 		ctx.page = BufferGetPage(ctx.buffer);
 
diff --git a/contrib/amcheck/verify_nbtree.c b/contrib/amcheck/verify_nbtree.c
index aac8c74f54..5a045b1d88 100644
--- a/contrib/amcheck/verify_nbtree.c
+++ b/contrib/amcheck/verify_nbtree.c
@@ -1249,7 +1249,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);
@@ -1273,7 +1273,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);
@@ -3440,7 +3440,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 ee8ebaf3ca..16bf039d7b 100644
--- a/contrib/bloom/blinsert.c
+++ b/contrib/bloom/blinsert.c
@@ -201,7 +201,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));
 
@@ -213,7 +213,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);
@@ -280,7 +280,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 bf801fe78f..30714944ac 100644
--- a/contrib/bloom/blscan.c
+++ b/contrib/bloom/blscan.c
@@ -123,7 +123,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 04b61042a5..e73fe580ce 100644
--- a/contrib/bloom/blutils.c
+++ b/contrib/bloom/blutils.c
@@ -185,7 +185,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);
@@ -364,7 +364,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
@@ -456,7 +456,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 9cdc8e182b..e7c3bf2f78 100644
--- a/contrib/pageinspect/btreefuncs.c
+++ b/contrib/pageinspect/btreefuncs.c
@@ -282,7 +282,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 */
@@ -422,7 +422,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 */
@@ -651,7 +651,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);
 
 		/*
@@ -875,7 +875,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 617dff821a..7605326c66 100644
--- a/contrib/pageinspect/rawpage.c
+++ b/contrib/pageinspect/rawpage.c
@@ -185,7 +185,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_prewarm/autoprewarm.c b/contrib/pg_prewarm/autoprewarm.c
index b45755b334..df335a054a 100644
--- a/contrib/pg_prewarm/autoprewarm.c
+++ b/contrib/pg_prewarm/autoprewarm.c
@@ -533,7 +533,7 @@ autoprewarm_database_main(Datum main_arg)
 
 		/* Prewarm buffer. */
 		buf = ReadBufferExtended(rel, blk->forknum, blk->blocknum, RBM_NORMAL,
-								 NULL);
+								 NULL, NULL);
 		if (BufferIsValid(buf))
 		{
 			apw_state->prewarmed_blocks++;
diff --git a/contrib/pg_surgery/heap_surgery.c b/contrib/pg_surgery/heap_surgery.c
index 5b94b3d523..62388c6175 100644
--- a/contrib/pg_surgery/heap_surgery.c
+++ b/contrib/pg_surgery/heap_surgery.c
@@ -172,7 +172,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 7f268a18a7..a11469660d 100644
--- a/contrib/pg_visibility/pg_visibility.c
+++ b/contrib/pg_visibility/pg_visibility.c
@@ -151,7 +151,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..a0a924acbf 100644
--- a/contrib/pgstattuple/pgstatindex.c
+++ b/contrib/pgstattuple/pgstatindex.c
@@ -250,7 +250,8 @@ 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 +287,8 @@ 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 +544,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 +647,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 48cb8f59c4..a15668dc11 100644
--- a/contrib/pgstattuple/pgstattuple.c
+++ b/contrib/pgstattuple/pgstattuple.c
@@ -373,7 +373,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);
@@ -386,7 +386,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);
@@ -411,7 +411,8 @@ 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);
 
@@ -497,7 +498,8 @@ 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 71c4f96d05..e3d1477ef0 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -3425,6 +3425,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 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 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 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 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>
@@ -4368,6 +4406,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 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 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 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 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>
@@ -4504,6 +4578,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 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 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 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 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 60320440fc..5c0e3febe5 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1647,7 +1647,7 @@ brinGetStats(Relation index, BrinStatsData *stats)
 	Page		metapage;
 	BrinMetaPageData *metadata;
 
-	metabuffer = ReadBuffer(index, BRIN_METAPAGE_BLKNO);
+	metabuffer = ReadBuffer(index, BRIN_METAPAGE_BLKNO, NULL);
 	LockBuffer(metabuffer, BUFFER_LOCK_SHARE);
 	metapage = BufferGetPage(metabuffer);
 	metadata = (BrinMetaPageData *) PageGetContents(metapage);
@@ -2182,7 +2182,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..c78f0cc4e7 100644
--- a/src/backend/access/brin/brin_pageops.c
+++ b/src/backend/access/brin/brin_pageops.c
@@ -739,7 +739,7 @@ brin_getinsertbuffer(Relation irel, Buffer oldbuf, Size itemsz,
 				LockRelationForExtension(irel, ExclusiveLock);
 				extensionLockHeld = true;
 			}
-			buf = ReadBuffer(irel, P_NEW);
+			buf = ReadBuffer(irel, P_NEW, NULL);
 			newblk = BufferGetBlockNumber(buf);
 			*extended = true;
 
@@ -756,7 +756,7 @@ brin_getinsertbuffer(Relation irel, Buffer oldbuf, Size itemsz,
 		}
 		else
 		{
-			buf = ReadBuffer(irel, newblk);
+			buf = ReadBuffer(irel, newblk, NULL);
 		}
 
 		/*
diff --git a/src/backend/access/brin/brin_revmap.c b/src/backend/access/brin/brin_revmap.c
index 4e380ecc71..4a0256d96a 100644
--- a/src/backend/access/brin/brin_revmap.c
+++ b/src/backend/access/brin/brin_revmap.c
@@ -74,7 +74,7 @@ brinRevmapInitialize(Relation idxrel, BlockNumber *pagesPerRange)
 	BrinMetaPageData *metadata;
 	Page		page;
 
-	meta = ReadBuffer(idxrel, BRIN_METAPAGE_BLKNO);
+	meta = ReadBuffer(idxrel, BRIN_METAPAGE_BLKNO, NULL);
 	LockBuffer(meta, BUFFER_LOCK_SHARE);
 	page = BufferGetPage(meta);
 	metadata = (BrinMetaPageData *) PageGetContents(page);
@@ -231,7 +231,7 @@ 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, NULL);
 		}
 
 		LockBuffer(revmap->rm_currBuf, BUFFER_LOCK_SHARE);
@@ -269,7 +269,7 @@ brinGetTupleForHeapBlock(BrinRevmap *revmap, BlockNumber heapBlk,
 		{
 			if (BufferIsValid(*buf))
 				ReleaseBuffer(*buf);
-			*buf = ReadBuffer(idxRel, blk);
+			*buf = ReadBuffer(idxRel, blk, NULL);
 		}
 		LockBuffer(*buf, mode);
 		page = BufferGetPage(*buf);
@@ -363,7 +363,7 @@ brinRevmapDesummarizeRange(Relation idxrel, BlockNumber heapBlk)
 		return true;
 	}
 
-	regBuf = ReadBuffer(idxrel, ItemPointerGetBlockNumber(iptr));
+	regBuf = ReadBuffer(idxrel, ItemPointerGetBlockNumber(iptr), NULL);
 	LockBuffer(regBuf, BUFFER_LOCK_EXCLUSIVE);
 	regPg = BufferGetPage(regBuf);
 
@@ -485,7 +485,7 @@ 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, NULL);
 	}
 
 	return revmap->rm_currBuf;
@@ -553,7 +553,7 @@ revmap_physical_extend(BrinRevmap *revmap)
 	nblocks = RelationGetNumberOfBlocks(irel);
 	if (mapBlk < nblocks)
 	{
-		buf = ReadBuffer(irel, mapBlk);
+		buf = ReadBuffer(irel, mapBlk, NULL);
 		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..ee04a27528 100644
--- a/src/backend/access/gin/ginbtree.c
+++ b/src/backend/access/gin/ginbtree.c
@@ -87,7 +87,7 @@ ginFindLeafPage(GinBtree btree, bool searchMode,
 
 	stack = (GinBtreeStack *) palloc(sizeof(GinBtreeStack));
 	stack->blkno = btree->rootBlkno;
-	stack->buffer = ReadBuffer(btree->index, btree->rootBlkno);
+	stack->buffer = ReadBuffer(btree->index, btree->rootBlkno, NULL);
 	stack->parent = NULL;
 	stack->predictNumber = 1;
 
@@ -148,7 +148,8 @@ 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,
+												 NULL);
 		}
 		else
 		{
@@ -157,7 +158,7 @@ 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, NULL);
 			stack->predictNumber = 1;
 		}
 	}
@@ -182,7 +183,7 @@ ginStepRight(Buffer buffer, Relation index, int lockmode)
 	bool		isData = GinPageIsData(page);
 	BlockNumber blkno = GinPageGetOpaque(page)->rightlink;
 
-	nextbuffer = ReadBuffer(index, blkno);
+	nextbuffer = ReadBuffer(index, blkno, NULL);
 	LockBuffer(nextbuffer, lockmode);
 	UnlockReleaseBuffer(buffer);
 
@@ -314,7 +315,7 @@ ginFindParents(GinBtree btree, GinBtreeStack *stack)
 
 		/* Descend down to next level */
 		blkno = leftmostBlkno;
-		buffer = ReadBuffer(btree->index, blkno);
+		buffer = ReadBuffer(btree->index, blkno, NULL);
 	}
 }
 
diff --git a/src/backend/access/gin/ginfast.c b/src/backend/access/gin/ginfast.c
index a6d88572cc..68e5112732 100644
--- a/src/backend/access/gin/ginfast.c
+++ b/src/backend/access/gin/ginfast.c
@@ -239,7 +239,7 @@ 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, NULL);
 	metapage = BufferGetPage(metabuffer);
 
 	/*
@@ -319,7 +319,7 @@ ginHeapTupleFastInsert(GinState *ginstate, GinTupleCollector *collector)
 			data.prevTail = metadata->tail;
 			data.newRightlink = sublist.head;
 
-			buffer = ReadBuffer(index, metadata->tail);
+			buffer = ReadBuffer(index, metadata->tail, NULL);
 			LockBuffer(buffer, GIN_EXCLUSIVE);
 			page = BufferGetPage(buffer);
 
@@ -358,7 +358,7 @@ ginHeapTupleFastInsert(GinState *ginstate, GinTupleCollector *collector)
 
 		CheckForSerializableConflictIn(index, NULL, GIN_METAPAGE_BLKNO);
 
-		buffer = ReadBuffer(index, metadata->tail);
+		buffer = ReadBuffer(index, metadata->tail, NULL);
 		LockBuffer(buffer, GIN_EXCLUSIVE);
 		page = BufferGetPage(buffer);
 
@@ -575,7 +575,7 @@ 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, NULL);
 			LockBuffer(buffers[data.ndeleted], GIN_EXCLUSIVE);
 			page = BufferGetPage(buffers[data.ndeleted]);
 
@@ -827,7 +827,7 @@ ginInsertCleanup(GinState *ginstate, bool full_clean,
 		workMemory = work_mem;
 	}
 
-	metabuffer = ReadBuffer(index, GIN_METAPAGE_BLKNO);
+	metabuffer = ReadBuffer(index, GIN_METAPAGE_BLKNO, NULL);
 	LockBuffer(metabuffer, GIN_SHARE);
 	metapage = BufferGetPage(metabuffer);
 	metadata = GinPageGetMeta(metapage);
@@ -850,7 +850,7 @@ ginInsertCleanup(GinState *ginstate, bool full_clean,
 	 * Read and lock head of pending list
 	 */
 	blkno = metadata->head;
-	buffer = ReadBuffer(index, blkno);
+	buffer = ReadBuffer(index, blkno, NULL);
 	LockBuffer(buffer, GIN_SHARE);
 	page = BufferGetPage(buffer);
 
@@ -1003,7 +1003,7 @@ ginInsertCleanup(GinState *ginstate, bool full_clean,
 		 * Read next page in pending list
 		 */
 		vacuum_delay_point(false);
-		buffer = ReadBuffer(index, blkno);
+		buffer = ReadBuffer(index, blkno, NULL);
 		LockBuffer(buffer, GIN_SHARE);
 		page = BufferGetPage(buffer);
 	}
diff --git a/src/backend/access/gin/ginget.c b/src/backend/access/gin/ginget.c
index 63dd1f3679..ebcab38baf 100644
--- a/src/backend/access/gin/ginget.c
+++ b/src/backend/access/gin/ginget.c
@@ -1480,7 +1480,7 @@ 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, NULL);
 
 				LockBuffer(tmpbuf, GIN_SHARE);
 				UnlockReleaseBuffer(pos->pendingBuffer);
@@ -1827,7 +1827,7 @@ scanPendingInsert(IndexScanDesc scan, TIDBitmap *tbm, int64 *ntids)
 				match;
 	int			i;
 	pendingPosition pos;
-	Buffer		metabuffer = ReadBuffer(scan->indexRelation, GIN_METAPAGE_BLKNO);
+	Buffer		metabuffer = ReadBuffer(scan->indexRelation, GIN_METAPAGE_BLKNO, NULL);
 	Page		page;
 	BlockNumber blkno;
 
@@ -1854,7 +1854,7 @@ scanPendingInsert(IndexScanDesc scan, TIDBitmap *tbm, int64 *ntids)
 		return;
 	}
 
-	pos.pendingBuffer = ReadBuffer(scan->indexRelation, blkno);
+	pos.pendingBuffer = ReadBuffer(scan->indexRelation, blkno, NULL);
 	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 1f9e58c4f1..c12b44eaca 100644
--- a/src/backend/access/gin/ginutil.c
+++ b/src/backend/access/gin/ginutil.c
@@ -310,7 +310,7 @@ GinNewBuffer(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
@@ -627,7 +627,7 @@ ginGetStats(Relation index, GinStatsData *stats)
 	Page		metapage;
 	GinMetaPageData *metadata;
 
-	metabuffer = ReadBuffer(index, GIN_METAPAGE_BLKNO);
+	metabuffer = ReadBuffer(index, GIN_METAPAGE_BLKNO, NULL);
 	LockBuffer(metabuffer, GIN_SHARE);
 	metapage = BufferGetPage(metabuffer);
 	metadata = GinPageGetMeta(metapage);
@@ -654,7 +654,7 @@ ginUpdateStats(Relation index, const GinStatsData *stats, bool is_build)
 	Page		metapage;
 	GinMetaPageData *metadata;
 
-	metabuffer = ReadBuffer(index, GIN_METAPAGE_BLKNO);
+	metabuffer = ReadBuffer(index, GIN_METAPAGE_BLKNO, NULL);
 	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 4d858b65e1..d393f0c731 100644
--- a/src/backend/access/gist/gist.c
+++ b/src/backend/access/gist/gist.c
@@ -681,7 +681,7 @@ gistdoinsert(Relation r, IndexTuple itup, Size freespace,
 		}
 
 		if (XLogRecPtrIsInvalid(stack->lsn))
-			stack->buffer = ReadBuffer(state.r, stack->blkno);
+			stack->buffer = ReadBuffer(state.r, stack->blkno, NULL);
 
 		/*
 		 * Be optimistic and grab shared lock first. Swap it for an exclusive
@@ -932,7 +932,7 @@ 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, NULL);
 		LockBuffer(buffer, GIST_SHARE);
 		gistcheckpage(r, buffer);
 		page = (Page) BufferGetPage(buffer);
@@ -1085,7 +1085,7 @@ gistFindCorrectParent(Relation r, GISTInsertStack *child, bool is_build)
 			 */
 			break;
 		}
-		parent->buffer = ReadBuffer(r, parent->blkno);
+		parent->buffer = ReadBuffer(r, parent->blkno, NULL);
 		LockBuffer(parent->buffer, GIST_EXCLUSIVE);
 		gistcheckpage(r, parent->buffer);
 		parent->page = (Page) BufferGetPage(parent->buffer);
@@ -1110,7 +1110,7 @@ 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, NULL);
 		ptr->page = (Page) BufferGetPage(ptr->buffer);
 		ptr = ptr->parent;
 	}
@@ -1225,7 +1225,7 @@ 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, NULL);
 			LockBuffer(buf, GIST_EXCLUSIVE);
 		}
 		else
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 9e707167d9..20a2310dfc 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -966,7 +966,7 @@ gistProcessItup(GISTBuildState *buildstate, IndexTuple itup,
 		 * descend down to.
 		 */
 
-		buffer = ReadBuffer(indexrel, blkno);
+		buffer = ReadBuffer(indexrel, blkno, NULL);
 		LockBuffer(buffer, GIST_EXCLUSIVE);
 
 		page = (Page) BufferGetPage(buffer);
@@ -1029,7 +1029,7 @@ 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, NULL);
 		LockBuffer(buffer, GIST_EXCLUSIVE);
 		gistbufferinginserttuples(buildstate, buffer, level,
 								  &itup, 1, InvalidOffsetNumber,
@@ -1102,7 +1102,7 @@ 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, NULL);
 
 				LockBuffer(childbuf, GIST_SHARE);
 				gistMemorizeAllDownlinks(buildstate, childbuf);
@@ -1246,7 +1246,7 @@ gistBufferingFindCorrectParent(GISTBuildState *buildstate,
 		parent = *parentblkno;
 	}
 
-	buffer = ReadBuffer(buildstate->indexrel, parent);
+	buffer = ReadBuffer(buildstate->indexrel, parent, NULL);
 	page = BufferGetPage(buffer);
 	LockBuffer(buffer, GIST_EXCLUSIVE);
 	gistcheckpage(buildstate->indexrel, buffer);
@@ -1441,7 +1441,7 @@ gistGetMaxLevel(Relation index)
 		Page		page;
 		IndexTuple	itup;
 
-		buffer = ReadBuffer(index, blkno);
+		buffer = ReadBuffer(index, blkno, NULL);
 
 		/*
 		 * There's no concurrent access during index build, so locking is just
diff --git a/src/backend/access/gist/gistget.c b/src/backend/access/gist/gistget.c
index cc40e928e0..98c9356b4b 100644
--- a/src/backend/access/gist/gistget.c
+++ b/src/backend/access/gist/gistget.c
@@ -49,7 +49,7 @@ gistkillitems(IndexScanDesc scan)
 	Assert(!XLogRecPtrIsInvalid(so->curPageLSN));
 	Assert(so->killedItems != NULL);
 
-	buffer = ReadBuffer(scan->indexRelation, so->curBlkno);
+	buffer = ReadBuffer(scan->indexRelation, so->curBlkno, NULL);
 	if (!BufferIsValid(buffer))
 		return;
 
@@ -340,7 +340,7 @@ gistScanPage(IndexScanDesc scan, GISTSearchItem *pageItem,
 
 	Assert(!GISTSearchItemIsHeap(*pageItem));
 
-	buffer = ReadBuffer(scan->indexRelation, pageItem->blkno);
+	buffer = ReadBuffer(scan->indexRelation, pageItem->blkno, NULL);
 	LockBuffer(buffer, GIST_SHARE);
 	PredicateLockPage(r, BufferGetBlockNumber(buffer), scan->xs_snapshot);
 	gistcheckpage(scan->indexRelation, buffer);
diff --git a/src/backend/access/gist/gistutil.c b/src/backend/access/gist/gistutil.c
index dbc4ac639a..b4dcc1056f 100644
--- a/src/backend/access/gist/gistutil.c
+++ b/src/backend/access/gist/gistutil.c
@@ -833,7 +833,7 @@ gistNewBuffer(Relation r, Relation heaprel)
 		if (blkno == InvalidBlockNumber)
 			break;				/* nothing left in FSM */
 
-		buffer = ReadBuffer(r, blkno);
+		buffer = ReadBuffer(r, blkno, NULL);
 
 		/*
 		 * We have to guard against the possibility that someone else already
diff --git a/src/backend/access/gist/gistvacuum.c b/src/backend/access/gist/gistvacuum.c
index dd0d9d5006..f95e42f40a 100644
--- a/src/backend/access/gist/gistvacuum.c
+++ b/src/backend/access/gist/gistvacuum.c
@@ -286,7 +286,7 @@ restart:
 	vacuum_delay_point(false);
 
 	buffer = ReadBufferExtended(rel, MAIN_FORKNUM, blkno, RBM_NORMAL,
-								info->strategy);
+								info->strategy, NULL);
 
 	/*
 	 * We are not going to stay here for a long time, aggressively grab an
@@ -482,7 +482,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);
@@ -548,7 +548,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 02ec1126a4..c806df1eb9 100644
--- a/src/backend/access/hash/hash.c
+++ b/src/backend/access/hash/hash.c
@@ -512,7 +512,8 @@ 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,
+								 NULL);
 		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..33dbcfd2f1 100644
--- a/src/backend/access/hash/hashpage.c
+++ b/src/backend/access/hash/hashpage.c
@@ -74,7 +74,7 @@ _hash_getbuf(Relation rel, BlockNumber blkno, int access, int flags)
 	if (blkno == P_NEW)
 		elog(ERROR, "hash AM does not use P_NEW");
 
-	buf = ReadBuffer(rel, blkno);
+	buf = ReadBuffer(rel, blkno, NULL);
 
 	if (access != HASH_NOLOCK)
 		LockBuffer(buf, access);
@@ -100,7 +100,7 @@ _hash_getbuf_with_condlock_cleanup(Relation rel, BlockNumber blkno, int flags)
 	if (blkno == P_NEW)
 		elog(ERROR, "hash AM does not use P_NEW");
 
-	buf = ReadBuffer(rel, blkno);
+	buf = ReadBuffer(rel, blkno, NULL);
 
 	if (!ConditionalLockBufferForCleanup(buf))
 	{
@@ -140,7 +140,7 @@ _hash_getinitbuf(Relation rel, BlockNumber blkno)
 		elog(ERROR, "hash AM does not use P_NEW");
 
 	buf = ReadBufferExtended(rel, MAIN_FORKNUM, blkno, RBM_ZERO_AND_LOCK,
-							 NULL);
+							 NULL, NULL);
 
 	/* ref count and lock type are correct */
 
@@ -218,7 +218,7 @@ _hash_getnewbuf(Relation rel, BlockNumber blkno, ForkNumber forkNum)
 	else
 	{
 		buf = ReadBufferExtended(rel, forkNum, blkno, RBM_ZERO_AND_LOCK,
-								 NULL);
+								 NULL, NULL);
 	}
 
 	/* ref count and lock type are correct */
@@ -245,7 +245,7 @@ _hash_getbuf_with_strategy(Relation rel, BlockNumber blkno,
 	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, NULL);
 
 	if (access != HASH_NOLOCK)
 		LockBuffer(buf, access);
diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index fa7935a0ed..5a0aa9998c 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -1538,7 +1538,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.
@@ -1832,7 +1832,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);
 
@@ -2728,7 +2728,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);
 
 	/*
@@ -3257,7 +3257,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);
 
 	/*
@@ -4513,7 +4513,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);
 
 	/*
@@ -6009,7 +6009,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);
 
@@ -6100,7 +6100,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);
@@ -8181,7 +8181,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 c0bec01415..12167d9bb5 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -128,7 +128,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
@@ -2185,7 +2186,8 @@ heapam_scan_bitmap_next_block(TableScanDesc scan,
 	 */
 	hscan->rs_cbuf = ReleaseAndReadBuffer(hscan->rs_cbuf,
 										  scan->rs_rd,
-										  block);
+										  block,
+										  NULL);
 	hscan->rs_cblock = block;
 	buffer = hscan->rs_cbuf;
 	snapshot = scan->rs_snapshot;
@@ -2414,7 +2416,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 1af18a78a2..4e63579b61 100644
--- a/src/backend/access/heap/vacuumlazy.c
+++ b/src/backend/access/heap/vacuumlazy.c
@@ -3394,7 +3394,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 31fe1c3ade..c76f1fd2d5 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;
@@ -733,7 +734,7 @@ _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, NULL);
 				page = BufferGetPage(nbuf);
 				opaque = BTPageGetOpaque(page);
 				if (!P_IGNORE(opaque))
@@ -1040,7 +1041,9 @@ _bt_stepright(Relation rel, Relation heaprel, BTInsertState insertstate,
 	rblkno = opaque->btpo_next;
 	for (;;)
 	{
-		rbuf = _bt_relandgetbuf(rel, rbuf, rblkno, BT_WRITE);
+		bool hit;
+		rbuf = _bt_relandgetbuf(rel, rbuf, rblkno, BT_WRITE, &hit);
+		pgstat_count_buffer(rel, P_ISLEAF(opaque), hit);
 		page = BufferGetPage(rbuf);
 		opaque = BTPageGetOpaque(page);
 
@@ -1256,10 +1259,14 @@ _bt_insertonpg(Relation rel,
 		 */
 		if (unlikely(split_only_page))
 		{
+			bool hit;
+
 			Assert(!isleaf);
 			Assert(BufferIsValid(cbuf));
 
-			metabuf = _bt_getbuf(rel, BTREE_METAPAGE, BT_WRITE);
+			metabuf = _bt_getbuf(rel, BTREE_METAPAGE, BT_WRITE, &hit);
+			pgstat_count_buffer(rel, true, hit);
+
 			metapg = BufferGetPage(metabuf);
 			metad = BTPageGetMeta(metapg);
 
@@ -1890,7 +1897,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;
+		sbuf = _bt_getbuf(rel, oopaque->btpo_next, BT_WRITE, &hit);
+		pgstat_count_buffer(rel, P_ISLEAF(oopaque), hit);
 		spage = BufferGetPage(sbuf);
 		sopaque = BTPageGetOpaque(spage);
 		if (sopaque->btpo_prev != origpagenumber)
@@ -2247,12 +2256,14 @@ _bt_finish_split(Relation rel, Relation heaprel, Buffer lbuf, BTStack stack)
 	BTPageOpaque rpageop;
 	bool		wasroot;
 	bool		wasonly;
+	bool        hit;
 
 	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_buffer(rel, P_ISLEAF(lpageop), hit);
 	rpage = BufferGetPage(rbuf);
 	rpageop = BTPageGetOpaque(rpage);
 
@@ -2264,7 +2275,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_buffer(rel, true, hit);
 		metapg = BufferGetPage(metabuf);
 		metad = BTPageGetMeta(metapg);
 
@@ -2330,7 +2342,7 @@ _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, NULL);
 		page = BufferGetPage(buf);
 		opaque = BTPageGetOpaque(page);
 
@@ -2460,6 +2472,7 @@ _bt_newlevel(Relation rel, Relation heaprel, Buffer lbuf, Buffer rbuf)
 	Buffer		metabuf;
 	Page		metapg;
 	BTMetaPageData *metad;
+	bool        hit;
 
 	lbkno = BufferGetBlockNumber(lbuf);
 	rbkno = BufferGetBlockNumber(rbuf);
@@ -2472,7 +2485,8 @@ _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);
+	pgstat_count_buffer(rel, true, hit);
 	metapg = BufferGetPage(metabuf);
 	metad = BTPageGetMeta(metapg);
 
diff --git a/src/backend/access/nbtree/nbtpage.c b/src/backend/access/nbtree/nbtpage.c
index c79dd38ee1..9bcb341470 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;
 
 	/*
 	 * 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_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;
 
 	/*
 	 * 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_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;
 
 	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_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_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_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;
 
 	/*
 	 * 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_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_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;
 
-		metabuf = _bt_getbuf(rel, BTREE_METAPAGE, BT_READ);
+		metabuf = _bt_getbuf(rel, BTREE_METAPAGE, BT_READ, &hit);
+		pgstat_count_metadata_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;
 
-		metabuf = _bt_getbuf(rel, BTREE_METAPAGE, BT_READ);
+		metabuf = _bt_getbuf(rel, BTREE_METAPAGE, BT_READ, &hit);
+		pgstat_count_metadata_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);
@@ -1703,7 +1719,7 @@ _bt_leftsib_splitflag(Relation rel, BlockNumber leftsib, BlockNumber target)
 	if (leftsib == P_NONE)
 		return false;
 
-	buf = _bt_getbuf(rel, leftsib, BT_READ);
+	buf = _bt_getbuf(rel, leftsib, BT_READ, NULL);
 	page = BufferGetPage(buf);
 	opaque = BTPageGetOpaque(page);
 
@@ -1758,7 +1774,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 +2078,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 +2351,7 @@ _bt_unlink_halfdead_page(Relation rel, Buffer leafbuf, BlockNumber scanblkno,
 	uint32		targetlevel;
 	IndexTuple	leafhikey;
 	BlockNumber leaftopparent;
+	bool        hit;
 
 	page = BufferGetPage(leafbuf);
 	opaque = BTPageGetOpaque(page);
@@ -2374,7 +2391,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_buffer(rel, hit);
 		page = BufferGetPage(buf);
 		opaque = BTPageGetOpaque(page);
 		leftsib = opaque->btpo_prev;
@@ -2401,7 +2419,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 +2467,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_buffer(rel, !P_ISLEAF(opaque), hit);
 			page = BufferGetPage(lbuf);
 			opaque = BTPageGetOpaque(page);
 		}
@@ -2513,7 +2532,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_buffer(rel, !P_ISLEAF(opaque), hit);
 	page = BufferGetPage(rbuf);
 	opaque = BTPageGetOpaque(page);
 
@@ -2569,7 +2589,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_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 dc244ae24c..7aa24e1f12 100644
--- a/src/backend/access/nbtree/nbtree.c
+++ b/src/backend/access/nbtree/nbtree.c
@@ -1146,7 +1146,7 @@ backtrack:
 	 * buffer access strategy.
 	 */
 	buf = ReadBufferExtended(rel, MAIN_FORKNUM, blkno, RBM_NORMAL,
-							 info->strategy);
+							 info->strategy, NULL);
 	_bt_lockbuf(rel, buf, BT_READ);
 	page = BufferGetPage(buf);
 	opaque = NULL;
diff --git a/src/backend/access/nbtree/nbtsearch.c b/src/backend/access/nbtree/nbtsearch.c
index 472ce06f19..203816c418 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;
 
 		/*
 		 * 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_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;
 
 	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_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_buffer(rel, !P_ISLEAF(opaque), hit);
 			continue;
 		}
 		else
@@ -2200,6 +2205,8 @@ static bool
 _bt_readnextpage(IndexScanDesc scan, BlockNumber blkno,
 				 BlockNumber lastcurrblkno, ScanDirection dir, bool seized)
 {
+	bool hit;
+
 	Relation	rel = scan->indexRelation;
 	BTScanOpaque so = (BTScanOpaque) scan->opaque;
 
@@ -2246,7 +2253,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_buffer(rel, hit);
 		}
 		else
 		{
@@ -2342,10 +2350,11 @@ _bt_lock_and_validate_left(Relation rel, BlockNumber *blkno,
 		Page		page;
 		BTPageOpaque opaque;
 		int			tries;
+		bool        hit;
 
 		/* 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, NULL);
 		page = BufferGetPage(buf);
 		opaque = BTPageGetOpaque(page);
 
@@ -2372,7 +2381,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_buffer(rel, P_ISLEAF(opaque), hit);
 			page = BufferGetPage(buf);
 			opaque = BTPageGetOpaque(page);
 		}
@@ -2382,7 +2392,7 @@ _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, NULL);
 		page = BufferGetPage(buf);
 		opaque = BTPageGetOpaque(page);
 		if (P_ISDELETED(opaque))
@@ -2399,7 +2409,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_buffer(rel, !P_ISLEAF(opaque), hit);
 				page = BufferGetPage(buf);
 				opaque = BTPageGetOpaque(page);
 				if (!P_ISDELETED(opaque))
@@ -2456,6 +2467,7 @@ _bt_get_endpoint(Relation rel, uint32 level, bool rightmost)
 	OffsetNumber offnum;
 	BlockNumber blkno;
 	IndexTuple	itup;
+	bool        hit;
 
 	/*
 	 * If we are looking for a leaf page, okay to descend from fast root;
@@ -2488,7 +2500,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_buffer(rel, hit);
 			page = BufferGetPage(buf);
 			opaque = BTPageGetOpaque(page);
 		}
@@ -2511,7 +2524,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_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 693e43c674..a550faa84e 100644
--- a/src/backend/access/nbtree/nbtutils.c
+++ b/src/backend/access/nbtree/nbtutils.c
@@ -2368,8 +2368,9 @@ _bt_killitems(IndexScanDesc scan)
 		Buffer		buf;
 
 		droppedpin = true;
-		/* Attempt to re-read the buffer, getting pin and lock. */
-		buf = _bt_getbuf(scan->indexRelation, so->currPos.currPage, BT_READ);
+		/* Attempt to re-read the buffer, getting pin andlock. */
+		buf = _bt_getbuf(scan->indexRelation, so->currPos.currPage, BT_READ,
+						 NULL);
 
 		page = BufferGetPage(buf);
 		if (BufferGetLSNAtomic(buf) == so->currPos.lsn)
diff --git a/src/backend/access/spgist/spgdoinsert.c b/src/backend/access/spgist/spgdoinsert.c
index af6b27b213..96ca5a91a2 100644
--- a/src/backend/access/spgist/spgdoinsert.c
+++ b/src/backend/access/spgist/spgdoinsert.c
@@ -2065,13 +2065,13 @@ 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, NULL);
 			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, NULL);
 
 			/*
 			 * 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 53f910e9d8..85f6e91af0 100644
--- a/src/backend/access/spgist/spgscan.c
+++ b/src/backend/access/spgist/spgscan.c
@@ -847,13 +847,13 @@ redirect:
 
 			if (buffer == InvalidBuffer)
 			{
-				buffer = ReadBuffer(index, blkno);
+				buffer = ReadBuffer(index, blkno, NULL);
 				LockBuffer(buffer, BUFFER_LOCK_SHARE);
 			}
 			else if (blkno != BufferGetBlockNumber(buffer))
 			{
 				UnlockReleaseBuffer(buffer);
-				buffer = ReadBuffer(index, blkno);
+				buffer = ReadBuffer(index, blkno, NULL);
 				LockBuffer(buffer, BUFFER_LOCK_SHARE);
 			}
 
diff --git a/src/backend/access/spgist/spgutils.c b/src/backend/access/spgist/spgutils.c
index 367c36ef9a..ec0c455e6d 100644
--- a/src/backend/access/spgist/spgutils.c
+++ b/src/backend/access/spgist/spgutils.c
@@ -267,7 +267,7 @@ spgGetCache(Relation index)
 			Buffer		metabuffer;
 			SpGistMetaPageData *metadata;
 
-			metabuffer = ReadBuffer(index, SPGIST_METAPAGE_BLKNO);
+			metabuffer = ReadBuffer(index, SPGIST_METAPAGE_BLKNO, NULL);
 			LockBuffer(metabuffer, BUFFER_LOCK_SHARE);
 
 			metadata = SpGistPageGetMeta(BufferGetPage(metabuffer));
@@ -407,7 +407,7 @@ SpGistNewBuffer(Relation index)
 		if (SpGistBlockIsFixed(blkno))
 			continue;
 
-		buffer = ReadBuffer(index, blkno);
+		buffer = ReadBuffer(index, blkno, NULL);
 
 		/*
 		 * We have to guard against the possibility that someone else already
@@ -452,7 +452,7 @@ SpGistUpdateMetaPage(Relation index)
 	{
 		Buffer		metabuffer;
 
-		metabuffer = ReadBuffer(index, SPGIST_METAPAGE_BLKNO);
+		metabuffer = ReadBuffer(index, SPGIST_METAPAGE_BLKNO, NULL);
 
 		if (ConditionalLockBuffer(metabuffer))
 		{
@@ -601,7 +601,7 @@ SpGistGetBuffer(Relation index, int flags, int needSpace, bool *isNew)
 		Buffer		buffer;
 		Page		page;
 
-		buffer = ReadBuffer(index, lup->blkno);
+		buffer = ReadBuffer(index, lup->blkno, NULL);
 
 		if (!ConditionalLockBuffer(buffer))
 		{
diff --git a/src/backend/access/spgist/spgvacuum.c b/src/backend/access/spgist/spgvacuum.c
index eeddacd0d5..b4cf3470b6 100644
--- a/src/backend/access/spgist/spgvacuum.c
+++ b/src/backend/access/spgist/spgvacuum.c
@@ -628,7 +628,7 @@ spgvacuumpage(spgBulkDeleteState *bds, BlockNumber blkno)
 	vacuum_delay_point(false);
 
 	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);
 
@@ -709,7 +709,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 14d583ae7a..8efecc6e19 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 eff0990957..7567e4987f 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -758,6 +758,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,
@@ -771,7 +775,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) -
@@ -828,7 +842,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
@@ -1062,6 +1082,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 4b7c5113aa..270c9bf826 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 99e44ed99f..03fbc85eac 100644
--- a/src/backend/storage/aio/read_stream.c
+++ b/src/backend/storage/aio/read_stream.c
@@ -267,7 +267,8 @@ read_stream_start_pending_read(ReadStream *stream, bool suppress_advice)
 								 &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. */
@@ -659,7 +660,8 @@ read_stream_next_buffer(ReadStream *stream, void **per_buffer_data)
 										&stream->buffers[oldest_buffer_index],
 										next_blocknum,
 										stream->advice_enabled ?
-										READ_BUFFERS_ISSUE_ADVICE : 0)))
+										READ_BUFFERS_ISSUE_ADVICE : 0,
+										NULL)))
 			{
 				/* Fast return. */
 				return buffer;
diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index 75cfc2b6fe..6acb8089a5 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -486,7 +486,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,
@@ -743,9 +744,9 @@ 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);
 }
 
 /*
@@ -791,7 +792,7 @@ 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;
 
@@ -810,7 +811,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;
 }
@@ -836,7 +837,7 @@ ReadBufferWithoutRelcache(RelFileLocator rlocator, ForkNumber forkNum,
 	return ReadBuffer_common(NULL, smgr,
 							 permanent ? RELPERSISTENCE_PERMANENT : RELPERSISTENCE_UNLOGGED,
 							 forkNum, blockNum,
-							 mode, strategy);
+							 mode, strategy, NULL);
 }
 
 /*
@@ -1004,7 +1005,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;
@@ -1109,7 +1110,8 @@ PinBufferForBlock(Relation rel,
 				  ForkNumber forkNum,
 				  BlockNumber blockNum,
 				  BufferAccessStrategy strategy,
-				  bool *foundPtr)
+				  bool *foundPtr,
+				  bool *hit)
 {
 	BufferDesc *bufHdr;
 	IOContext	io_context;
@@ -1160,8 +1162,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)
 	{
@@ -1189,7 +1194,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;
@@ -1227,7 +1233,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;
 	}
@@ -1244,7 +1250,8 @@ ReadBuffer_common(Relation rel, SMgrRelation smgr, char smgr_persistence,
 	if (StartReadBuffer(&operation,
 						&buffer,
 						blockNum,
-						flags))
+						flags,
+						hit))
 		WaitReadBuffers(&operation);
 
 	return buffer;
@@ -1255,7 +1262,8 @@ StartReadBuffersImpl(ReadBuffersOperation *operation,
 					 Buffer *buffers,
 					 BlockNumber blockNum,
 					 int *nblocks,
-					 int flags)
+					 int flags,
+					 bool *hit)
 {
 	int			actual_nblocks = *nblocks;
 	int			io_buffers_len = 0;
@@ -1274,7 +1282,8 @@ StartReadBuffersImpl(ReadBuffersOperation *operation,
 									   operation->forknum,
 									   blockNum + i,
 									   operation->strategy,
-									   &found);
+									   &found,
+									   hit);
 
 		if (found)
 		{
@@ -1365,9 +1374,10 @@ StartReadBuffers(ReadBuffersOperation *operation,
 				 Buffer *buffers,
 				 BlockNumber blockNum,
 				 int *nblocks,
-				 int flags)
+				 int flags,
+				 bool *hit)
 {
-	return StartReadBuffersImpl(operation, buffers, blockNum, nblocks, flags);
+	return StartReadBuffersImpl(operation, buffers, blockNum, nblocks, flags, hit);
 }
 
 /*
@@ -1379,12 +1389,13 @@ 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);
+	result = StartReadBuffersImpl(operation, buffer, blocknum, &nblocks, flags, hit);
 	Assert(nblocks == 1);		/* single block can't be short */
 
 	return result;
@@ -2590,7 +2601,8 @@ MarkBufferDirty(Buffer buffer)
 Buffer
 ReleaseAndReadBuffer(Buffer buffer,
 					 Relation relation,
-					 BlockNumber blockNum)
+					 BlockNumber blockNum,
+					 bool *hit)
 {
 	ForkNumber	forkNum = MAIN_FORKNUM;
 	BufferDesc *bufHdr;
@@ -2619,7 +2631,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 05a8ccfdb7..bbfd75715d 100644
--- a/src/backend/utils/activity/pgstat_database.c
+++ b/src/backend/utils/activity/pgstat_database.c
@@ -407,6 +407,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 d64595a165..c879c3b2a6 100644
--- a/src/backend/utils/activity/pgstat_relation.c
+++ b/src/backend/utils/activity/pgstat_relation.c
@@ -871,6 +871,10 @@ pgstat_relation_flush_cb(PgStat_EntryRef *entry_ref, bool nowait)
 	tabentry->ins_since_vacuum += lstats->counts.tuples_inserted;
 	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);
@@ -888,6 +892,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 e9096a8849..565a96773d 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)
 
@@ -1031,6 +1043,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 000c7289b8..9d7e2a6e45 100644
--- a/src/include/access/nbtree.h
+++ b/src/include/access/nbtree.h
@@ -1247,10 +1247,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 9e803d610d..5902827510 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5517,6 +5517,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',
@@ -5717,6 +5733,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 53f2a8458e..b846ef7529 100644
--- a/src/include/pgstat.h
+++ b/src/include/pgstat.h
@@ -154,6 +154,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;
 
 /* ----------
@@ -364,6 +369,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;
@@ -459,6 +468,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 */
@@ -707,6 +721,37 @@ 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_buffer(rel, hit)					    \
+	do {															\
+		if (pgstat_should_count_relation(rel)) {                    \
+			(rel)->pgstat_info->counts.metadata_blocks_fetched++;   \
+			if ((hit)) 												\
+				(rel)->pgstat_info->counts.metadata_blocks_hit++;   \
+		}       												    \
+	} while (0)
+#define pgstat_count_record_buffer(rel, hit)					    \
+	do {															\
+		if (pgstat_should_count_relation(rel)) {                    \
+			(rel)->pgstat_info->counts.record_blocks_fetched++;     \
+			if ((hit)) 												\
+				(rel)->pgstat_info->counts.record_blocks_hit++;     \
+		}       												    \
+	} while (0)
+#define pgstat_count_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 7c1e4316dd..8f287f0841 100644
--- a/src/include/storage/bufmgr.h
+++ b/src/include/storage/bufmgr.h
@@ -201,10 +201,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,
@@ -213,12 +213,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);
@@ -229,7 +231,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/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 5baba8d39f..5217cc74a9 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1847,6 +1847,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,
@@ -2342,7 +2346,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)))
@@ -2363,6 +2371,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,
@@ -2371,7 +2383,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,
@@ -2385,7 +2401,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,
@@ -2402,6 +2422,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,
@@ -2414,7 +2438,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,
@@ -2431,6 +2459,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,
-- 
2.39.5 (Apple Git-154)