From e102c9d15c08c638879ece26008faee58cf4a07e Mon Sep 17 00:00:00 2001
From: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Date: Thu, 16 Nov 2023 02:30:01 +0000
Subject: [PATCH v1] Provide relfilenode statistics

---
 src/backend/access/rmgrdesc/xactdesc.c        |   5 +-
 src/backend/catalog/storage.c                 |   8 ++
 src/backend/catalog/system_functions.sql      |   2 +-
 src/backend/catalog/system_views.sql          |   5 +-
 src/backend/postmaster/checkpointer.c         |   5 +
 src/backend/storage/buffer/bufmgr.c           |   6 +-
 src/backend/storage/smgr/md.c                 |   7 ++
 src/backend/utils/activity/pgstat.c           |  39 ++++--
 src/backend/utils/activity/pgstat_database.c  |  12 +-
 src/backend/utils/activity/pgstat_function.c  |  13 +-
 src/backend/utils/activity/pgstat_relation.c  | 112 ++++++++++++++++--
 src/backend/utils/activity/pgstat_replslot.c  |  13 +-
 src/backend/utils/activity/pgstat_shmem.c     |  19 ++-
 .../utils/activity/pgstat_subscription.c      |  12 +-
 src/backend/utils/activity/pgstat_xact.c      |  60 +++++++---
 src/backend/utils/adt/pgstatfuncs.c           |  34 +++++-
 src/include/access/tableam.h                  |  19 +++
 src/include/access/xact.h                     |   1 +
 src/include/catalog/pg_proc.dat               |  14 ++-
 src/include/pgstat.h                          |  19 ++-
 src/include/utils/pgstat_internal.h           |  34 ++++--
 src/test/recovery/t/029_stats_restart.pl      |  40 +++----
 .../recovery/t/030_stats_cleanup_replica.pl   |   6 +-
 src/test/regress/expected/rules.out           |  12 +-
 src/test/regress/expected/stats.out           |  30 ++---
 src/test/regress/sql/stats.sql                |  30 ++---
 src/test/subscription/t/026_stats.pl          |   4 +-
 src/tools/pgindent/typedefs.list              |   1 +
 28 files changed, 415 insertions(+), 147 deletions(-)
   4.6% src/backend/catalog/
  47.8% src/backend/utils/activity/
   6.5% src/backend/utils/adt/
   3.7% src/backend/
   3.3% src/include/access/
   3.3% src/include/catalog/
   6.2% src/include/utils/
   3.3% src/include/
  12.1% src/test/recovery/t/
   5.5% src/test/regress/expected/
   3.0% src/test/

diff --git a/src/backend/access/rmgrdesc/xactdesc.c b/src/backend/access/rmgrdesc/xactdesc.c
index dccca201e0..c02b079645 100644
--- a/src/backend/access/rmgrdesc/xactdesc.c
+++ b/src/backend/access/rmgrdesc/xactdesc.c
@@ -319,10 +319,11 @@ xact_desc_stats(StringInfo buf, const char *label,
 		appendStringInfo(buf, "; %sdropped stats:", label);
 		for (i = 0; i < ndropped; i++)
 		{
-			appendStringInfo(buf, " %d/%u/%u",
+			appendStringInfo(buf, " %d/%u/%u/%u",
 							 dropped_stats[i].kind,
 							 dropped_stats[i].dboid,
-							 dropped_stats[i].objoid);
+							 dropped_stats[i].objoid,
+							 dropped_stats[i].relfile);
 		}
 	}
 }
diff --git a/src/backend/catalog/storage.c b/src/backend/catalog/storage.c
index f56b3cc0f2..db6107cd90 100644
--- a/src/backend/catalog/storage.c
+++ b/src/backend/catalog/storage.c
@@ -33,6 +33,7 @@
 #include "storage/smgr.h"
 #include "utils/hsearch.h"
 #include "utils/memutils.h"
+#include "utils/pgstat_internal.h"
 #include "utils/rel.h"
 
 /* GUC variables */
@@ -152,6 +153,7 @@ RelationCreateStorage(RelFileLocator rlocator, char relpersistence,
 	if (needs_wal)
 		log_smgrcreate(&srel->smgr_rlocator.locator, MAIN_FORKNUM);
 
+	pgstat_create_transactional(PGSTAT_KIND_RELFILENODE, rlocator.dbOid, rlocator.spcOid, rlocator.relNumber);
 	/*
 	 * Add the relation to the list of stuff to delete at abort, if we are
 	 * asked to do so.
@@ -227,6 +229,8 @@ RelationDropStorage(Relation rel)
 	 * for now I'll keep the logic simple.
 	 */
 
+	pgstat_drop_transactional(PGSTAT_KIND_RELFILENODE, rel->rd_locator.dbOid, rel->rd_locator.spcOid,  rel->rd_locator.relNumber);
+
 	RelationCloseSmgr(rel);
 }
 
@@ -253,6 +257,9 @@ RelationPreserveStorage(RelFileLocator rlocator, bool atCommit)
 	PendingRelDelete *pending;
 	PendingRelDelete *prev;
 	PendingRelDelete *next;
+	PgStat_SubXactStatus *xact_state;
+
+	xact_state = pgStatXactStack;
 
 	prev = NULL;
 	for (pending = pendingDeletes; pending != NULL; pending = next)
@@ -267,6 +274,7 @@ RelationPreserveStorage(RelFileLocator rlocator, bool atCommit)
 			else
 				pendingDeletes = next;
 			pfree(pending);
+			PgStat_RemoveRelFileNodeFromDroppedStats(xact_state, rlocator);
 			/* prev does not change */
 		}
 		else
diff --git a/src/backend/catalog/system_functions.sql b/src/backend/catalog/system_functions.sql
index ae099e328c..140c8d556c 100644
--- a/src/backend/catalog/system_functions.sql
+++ b/src/backend/catalog/system_functions.sql
@@ -681,7 +681,7 @@ REVOKE EXECUTE ON FUNCTION pg_stat_reset_single_function_counters(oid) FROM publ
 
 REVOKE EXECUTE ON FUNCTION pg_stat_reset_replication_slot(text) FROM public;
 
-REVOKE EXECUTE ON FUNCTION pg_stat_have_stats(text, oid, oid) FROM public;
+REVOKE EXECUTE ON FUNCTION pg_stat_have_stats(text, oid, oid, oid) FROM public;
 
 REVOKE EXECUTE ON FUNCTION pg_stat_reset_subscription_stats(oid) FROM public;
 
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 53047cab5f..b0d7af6df0 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -750,6 +750,7 @@ CREATE VIEW pg_statio_all_tables AS
             C.relname AS relname,
             pg_stat_get_blocks_fetched(C.oid) -
                     pg_stat_get_blocks_hit(C.oid) AS heap_blks_read,
+			pg_stat_get_blocks_written(C.oid) + pg_stat_get_relfilenode_blocks_written(d.oid, CASE WHEN C.reltablespace <> 0 THEN C.reltablespace ELSE d.dattablespace END, C.relfilenode) AS heap_blks_written,
             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,
@@ -758,7 +759,7 @@ CREATE VIEW pg_statio_all_tables AS
             pg_stat_get_blocks_hit(T.oid) AS toast_blks_hit,
             X.idx_blks_read AS tidx_blks_read,
             X.idx_blks_hit AS tidx_blks_hit
-    FROM pg_class C LEFT JOIN
+    FROM pg_database d, pg_class C LEFT JOIN
             pg_class T ON C.reltoastrelid = T.oid
             LEFT JOIN pg_namespace N ON (N.oid = C.relnamespace)
             LEFT JOIN LATERAL (
@@ -775,7 +776,7 @@ CREATE VIEW pg_statio_all_tables AS
                      sum(pg_stat_get_blocks_hit(indexrelid))::bigint
                      AS idx_blks_hit
               FROM pg_index WHERE indrelid = T.oid ) X ON true
-    WHERE C.relkind IN ('r', 't', 'm');
+    WHERE C.relkind IN ('r', 't', 'm') AND d.datname = current_database();
 
 CREATE VIEW pg_statio_sys_tables AS
     SELECT * FROM pg_statio_all_tables
diff --git a/src/backend/postmaster/checkpointer.c b/src/backend/postmaster/checkpointer.c
index 3c68a9904d..0ff2812218 100644
--- a/src/backend/postmaster/checkpointer.c
+++ b/src/backend/postmaster/checkpointer.c
@@ -519,6 +519,11 @@ CheckpointerMain(char *startup_data, size_t startup_data_len)
 		/* Report pending statistics to the cumulative stats system */
 		pgstat_report_checkpointer();
 		pgstat_report_wal(true);
+		/*
+		 *  No need to check for transaction state in checkpointer before
+		 *  calling pgstat_report_stat().
+		 */
+		pgstat_report_stat(true);
 
 		/*
 		 * If any checkpoint flags have been set, redo the loop to handle the
diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index 49637284f9..06d89ba26b 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -1121,9 +1121,9 @@ PinBufferForBlock(Relation rel,
 		 * WaitReadBuffers() (so, not for hits, and not for buffers that are
 		 * zeroed instead), the per-relation stats always count them.
 		 */
-		pgstat_count_buffer_read(rel);
+		pgstat_report_relfilenode_buffer_read(rel);
 		if (*foundPtr)
-			pgstat_count_buffer_hit(rel);
+			pgstat_report_relfilenode_buffer_hit(rel);
 	}
 	if (*foundPtr)
 	{
@@ -3838,6 +3838,8 @@ FlushBuffer(BufferDesc *buf, SMgrRelation reln, IOObject io_object,
 
 	pgBufferUsage.shared_blks_written++;
 
+	pgstat_report_relfilenode_blks_written(reln->smgr_rlocator.locator);
+
 	/*
 	 * Mark the buffer as clean (unless BM_JUST_DIRTIED has become set) and
 	 * end the BM_IO_IN_PROGRESS state.
diff --git a/src/backend/storage/smgr/md.c b/src/backend/storage/smgr/md.c
index bf0f3ca76d..3576749d2d 100644
--- a/src/backend/storage/smgr/md.c
+++ b/src/backend/storage/smgr/md.c
@@ -1447,12 +1447,16 @@ DropRelationFiles(RelFileLocator *delrels, int ndelrels, bool isRedo)
 {
 	SMgrRelation *srels;
 	int			i;
+	int         not_freed_count = 0;
 
 	srels = palloc(sizeof(SMgrRelation) * ndelrels);
 	for (i = 0; i < ndelrels; i++)
 	{
 		SMgrRelation srel = smgropen(delrels[i], INVALID_PROC_NUMBER);
 
+		if (!pgstat_drop_entry(PGSTAT_KIND_RELFILENODE, delrels[i].dbOid, delrels[i].spcOid, delrels[i].relNumber))
+			not_freed_count++;
+
 		if (isRedo)
 		{
 			ForkNumber	fork;
@@ -1463,6 +1467,9 @@ DropRelationFiles(RelFileLocator *delrels, int ndelrels, bool isRedo)
 		srels[i] = srel;
 	}
 
+	if (not_freed_count > 0)
+		pgstat_request_entry_refs_gc();
+
 	smgrdounlinkall(srels, ndelrels, isRedo);
 
 	for (i = 0; i < ndelrels; i++)
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index dcc2ad8d95..e3b6f45828 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -288,6 +288,19 @@ static const PgStat_KindInfo pgstat_kind_infos[PGSTAT_NUM_KINDS] = {
 		.delete_pending_cb = pgstat_relation_delete_pending_cb,
 	},
 
+	[PGSTAT_KIND_RELFILENODE] = {
+		.name = "relfilenode",
+
+		.fixed_amount = false,
+
+		.shared_size = sizeof(PgStatShared_RelFileNode),
+		.shared_data_off = offsetof(PgStatShared_RelFileNode, stats),
+		.shared_data_len = sizeof(((PgStatShared_RelFileNode *) 0)->stats),
+		.pending_size = sizeof(PgStat_StatRelFileNodeEntry),
+
+		.flush_pending_cb = pgstat_relfilenode_flush_cb,
+	},
+
 	[PGSTAT_KIND_FUNCTION] = {
 		.name = "function",
 
@@ -651,7 +664,7 @@ pgstat_report_stat(bool force)
 
 	partial_flush = false;
 
-	/* flush database / relation / function / ... stats */
+	/* flush database / relation / function / relfilenode / ... stats */
 	partial_flush |= pgstat_flush_pending_entries(nowait);
 
 	/* flush IO stats */
@@ -731,7 +744,7 @@ pgstat_reset_counters(void)
  * GRANT system.
  */
 void
-pgstat_reset(PgStat_Kind kind, Oid dboid, Oid objoid)
+pgstat_reset(PgStat_Kind kind, Oid dboid, Oid objoid, RelFileNumber relfile)
 {
 	const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
 	TimestampTz ts = GetCurrentTimestamp();
@@ -740,7 +753,7 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, Oid objoid)
 	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
 
 	/* reset the "single counter" */
-	pgstat_reset_entry(kind, dboid, objoid, ts);
+	pgstat_reset_entry(kind, dboid, objoid, relfile, ts);
 
 	if (!kind_info->accessed_across_databases)
 		pgstat_reset_database_timestamp(dboid, ts);
@@ -809,7 +822,7 @@ pgstat_clear_snapshot(void)
 }
 
 void *
-pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, Oid objoid)
+pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, Oid objoid, RelFileNumber relfile)
 {
 	PgStat_HashKey key;
 	PgStat_EntryRef *entry_ref;
@@ -825,6 +838,7 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, Oid objoid)
 	key.kind = kind;
 	key.dboid = dboid;
 	key.objoid = objoid;
+	key.relfile = relfile;
 
 	/* if we need to build a full snapshot, do so */
 	if (pgstat_fetch_consistency == PGSTAT_FETCH_CONSISTENCY_SNAPSHOT)
@@ -850,7 +864,7 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, Oid objoid)
 
 	pgStatLocal.snapshot.mode = pgstat_fetch_consistency;
 
-	entry_ref = pgstat_get_entry_ref(kind, dboid, objoid, false, NULL);
+	entry_ref = pgstat_get_entry_ref(kind, dboid, objoid, relfile, false, NULL);
 
 	if (entry_ref == NULL || entry_ref->shared_entry->dropped)
 	{
@@ -919,13 +933,13 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot)
 }
 
 bool
-pgstat_have_entry(PgStat_Kind kind, Oid dboid, Oid objoid)
+pgstat_have_entry(PgStat_Kind kind, Oid dboid, Oid objoid, RelFileNumber relfile)
 {
 	/* fixed-numbered stats always exist */
 	if (pgstat_get_kind_info(kind)->fixed_amount)
 		return true;
 
-	return pgstat_get_entry_ref(kind, dboid, objoid, false, NULL) != NULL;
+	return pgstat_get_entry_ref(kind, dboid, objoid, relfile, false, NULL) != NULL;
 }
 
 /*
@@ -1102,7 +1116,8 @@ pgstat_build_snapshot_fixed(PgStat_Kind kind)
  * created, false otherwise.
  */
 PgStat_EntryRef *
-pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, Oid objoid, bool *created_entry)
+pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, Oid objoid,
+						  RelFileNumber relfile, bool *created_entry)
 {
 	PgStat_EntryRef *entry_ref;
 
@@ -1117,7 +1132,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, Oid objoid, bool *created
 								  ALLOCSET_SMALL_SIZES);
 	}
 
-	entry_ref = pgstat_get_entry_ref(kind, dboid, objoid,
+	entry_ref = pgstat_get_entry_ref(kind, dboid, objoid, relfile,
 									 true, created_entry);
 
 	if (entry_ref->pending == NULL)
@@ -1140,11 +1155,11 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, Oid objoid, bool *created
  * that it shouldn't be needed.
  */
 PgStat_EntryRef *
-pgstat_fetch_pending_entry(PgStat_Kind kind, Oid dboid, Oid objoid)
+pgstat_fetch_pending_entry(PgStat_Kind kind, Oid dboid, Oid objoid, RelFileNumber relfile)
 {
 	PgStat_EntryRef *entry_ref;
 
-	entry_ref = pgstat_get_entry_ref(kind, dboid, objoid, false, NULL);
+	entry_ref = pgstat_get_entry_ref(kind, dboid, objoid, relfile, false, NULL);
 
 	if (entry_ref == NULL || entry_ref->pending == NULL)
 		return NULL;
@@ -1173,7 +1188,7 @@ pgstat_delete_pending_entry(PgStat_EntryRef *entry_ref)
 }
 
 /*
- * Flush out pending stats for database objects (databases, relations,
+ * Flush out pending stats for database objects (databases, relations, relfilenodes,
  * functions).
  */
 static bool
diff --git a/src/backend/utils/activity/pgstat_database.c b/src/backend/utils/activity/pgstat_database.c
index 29bc090974..cf77f2dbdb 100644
--- a/src/backend/utils/activity/pgstat_database.c
+++ b/src/backend/utils/activity/pgstat_database.c
@@ -43,7 +43,7 @@ static PgStat_Counter pgLastSessionReportTime = 0;
 void
 pgstat_drop_database(Oid databaseid)
 {
-	pgstat_drop_transactional(PGSTAT_KIND_DATABASE, databaseid, InvalidOid);
+	pgstat_drop_transactional(PGSTAT_KIND_DATABASE, databaseid, InvalidOid, InvalidOid);
 }
 
 /*
@@ -66,7 +66,7 @@ pgstat_report_autovac(Oid dboid)
 	 * operation so it doesn't matter if we get blocked here a little.
 	 */
 	entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_DATABASE,
-											dboid, InvalidOid, false);
+											dboid, InvalidOid, InvalidOid, false);
 
 	dbentry = (PgStatShared_Database *) entry_ref->shared_stats;
 	dbentry->stats.last_autovac_time = GetCurrentTimestamp();
@@ -150,7 +150,7 @@ pgstat_report_checksum_failures_in_db(Oid dboid, int failurecount)
 	 * common enough for that to be a problem.
 	 */
 	entry_ref =
-		pgstat_get_entry_ref_locked(PGSTAT_KIND_DATABASE, dboid, InvalidOid, false);
+		pgstat_get_entry_ref_locked(PGSTAT_KIND_DATABASE, dboid, InvalidOid, InvalidOid, false);
 
 	sharedent = (PgStatShared_Database *) entry_ref->shared_stats;
 	sharedent->stats.checksum_failures += failurecount;
@@ -242,7 +242,7 @@ PgStat_StatDBEntry *
 pgstat_fetch_stat_dbentry(Oid dboid)
 {
 	return (PgStat_StatDBEntry *)
-		pgstat_fetch_entry(PGSTAT_KIND_DATABASE, dboid, InvalidOid);
+		pgstat_fetch_entry(PGSTAT_KIND_DATABASE, dboid, InvalidOid, InvalidOid);
 }
 
 void
@@ -341,7 +341,7 @@ pgstat_prep_database_pending(Oid dboid)
 	Assert(!OidIsValid(dboid) || OidIsValid(MyDatabaseId));
 
 	entry_ref = pgstat_prep_pending_entry(PGSTAT_KIND_DATABASE, dboid, InvalidOid,
-										  NULL);
+										  InvalidOid, NULL);
 
 	return entry_ref->pending;
 }
@@ -357,7 +357,7 @@ pgstat_reset_database_timestamp(Oid dboid, TimestampTz ts)
 	PgStatShared_Database *dbentry;
 
 	dbref = pgstat_get_entry_ref_locked(PGSTAT_KIND_DATABASE, MyDatabaseId, InvalidOid,
-										false);
+										InvalidOid, false);
 
 	dbentry = (PgStatShared_Database *) dbref->shared_stats;
 	dbentry->stats.stat_reset_timestamp = ts;
diff --git a/src/backend/utils/activity/pgstat_function.c b/src/backend/utils/activity/pgstat_function.c
index d26da551a4..440e44e300 100644
--- a/src/backend/utils/activity/pgstat_function.c
+++ b/src/backend/utils/activity/pgstat_function.c
@@ -46,7 +46,8 @@ pgstat_create_function(Oid proid)
 {
 	pgstat_create_transactional(PGSTAT_KIND_FUNCTION,
 								MyDatabaseId,
-								proid);
+								proid,
+								InvalidOid);
 }
 
 /*
@@ -61,7 +62,8 @@ pgstat_drop_function(Oid proid)
 {
 	pgstat_drop_transactional(PGSTAT_KIND_FUNCTION,
 							  MyDatabaseId,
-							  proid);
+							  proid,
+							  InvalidOid);
 }
 
 /*
@@ -86,6 +88,7 @@ pgstat_init_function_usage(FunctionCallInfo fcinfo,
 	entry_ref = pgstat_prep_pending_entry(PGSTAT_KIND_FUNCTION,
 										  MyDatabaseId,
 										  fcinfo->flinfo->fn_oid,
+										  InvalidOid,
 										  &created_entry);
 
 	/*
@@ -113,7 +116,7 @@ pgstat_init_function_usage(FunctionCallInfo fcinfo,
 		if (!SearchSysCacheExists1(PROCOID, ObjectIdGetDatum(fcinfo->flinfo->fn_oid)))
 		{
 			pgstat_drop_entry(PGSTAT_KIND_FUNCTION, MyDatabaseId,
-							  fcinfo->flinfo->fn_oid);
+							  fcinfo->flinfo->fn_oid, InvalidOid);
 			ereport(ERROR, errcode(ERRCODE_UNDEFINED_FUNCTION),
 					errmsg("function call to dropped function"));
 		}
@@ -224,7 +227,7 @@ find_funcstat_entry(Oid func_id)
 {
 	PgStat_EntryRef *entry_ref;
 
-	entry_ref = pgstat_fetch_pending_entry(PGSTAT_KIND_FUNCTION, MyDatabaseId, func_id);
+	entry_ref = pgstat_fetch_pending_entry(PGSTAT_KIND_FUNCTION, MyDatabaseId, func_id, InvalidOid);
 
 	if (entry_ref)
 		return entry_ref->pending;
@@ -239,5 +242,5 @@ PgStat_StatFuncEntry *
 pgstat_fetch_stat_funcentry(Oid func_id)
 {
 	return (PgStat_StatFuncEntry *)
-		pgstat_fetch_entry(PGSTAT_KIND_FUNCTION, MyDatabaseId, func_id);
+		pgstat_fetch_entry(PGSTAT_KIND_FUNCTION, MyDatabaseId, func_id, InvalidOid);
 }
diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c
index 8a3f7d434c..136dd6c85b 100644
--- a/src/backend/utils/activity/pgstat_relation.c
+++ b/src/backend/utils/activity/pgstat_relation.c
@@ -44,6 +44,7 @@ typedef struct TwoPhasePgStatRecord
 
 
 static PgStat_TableStatus *pgstat_prep_relation_pending(Oid rel_id, bool isshared);
+PgStat_StatRelFileNodeEntry *pgstat_prep_relfilenode_pending(RelFileLocator locator);
 static void add_tabstat_xact_level(PgStat_TableStatus *pgstat_info, int nest_level);
 static void ensure_tabstat_xact_level(PgStat_TableStatus *pgstat_info);
 static void save_truncdrop_counters(PgStat_TableXactStatus *trans, bool is_drop);
@@ -69,6 +70,7 @@ pgstat_copy_relation_stats(Relation dst, Relation src)
 	dst_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION,
 										  dst->rd_rel->relisshared ? InvalidOid : MyDatabaseId,
 										  RelationGetRelid(dst),
+										  InvalidOid,
 										  false);
 
 	dstshstats = (PgStatShared_Relation *) dst_ref->shared_stats;
@@ -170,7 +172,7 @@ pgstat_create_relation(Relation rel)
 {
 	pgstat_create_transactional(PGSTAT_KIND_RELATION,
 								rel->rd_rel->relisshared ? InvalidOid : MyDatabaseId,
-								RelationGetRelid(rel));
+								RelationGetRelid(rel), InvalidOid);
 }
 
 /*
@@ -184,7 +186,7 @@ pgstat_drop_relation(Relation rel)
 
 	pgstat_drop_transactional(PGSTAT_KIND_RELATION,
 							  rel->rd_rel->relisshared ? InvalidOid : MyDatabaseId,
-							  RelationGetRelid(rel));
+							  RelationGetRelid(rel), InvalidOid);
 
 	if (!pgstat_should_count_relation(rel))
 		return;
@@ -225,7 +227,7 @@ pgstat_report_vacuum(Oid tableoid, bool shared,
 
 	/* block acquiring lock for the same reason as pgstat_report_autovac() */
 	entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION,
-											dboid, tableoid, false);
+											dboid, tableoid, InvalidOid, false);
 
 	shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats;
 	tabentry = &shtabentry->stats;
@@ -318,6 +320,7 @@ pgstat_report_analyze(Relation rel,
 	/* block acquiring lock for the same reason as pgstat_report_autovac() */
 	entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, dboid,
 											RelationGetRelid(rel),
+											InvalidOid,
 											false);
 	/* can't get dropped while accessed */
 	Assert(entry_ref != NULL && entry_ref->shared_stats != NULL);
@@ -458,6 +461,19 @@ pgstat_fetch_stat_tabentry(Oid relid)
 	return pgstat_fetch_stat_tabentry_ext(IsSharedRelation(relid), relid);
 }
 
+/*
+ * Support function for the SQL-callable pgstat* functions. Returns
+ * the collected statistics for one relfilenode or NULL. NULL doesn't mean
+ * that the relfilenode doesn't exist, just that there are no statistics, so the
+ * caller is better off to report ZERO instead.
+ */
+PgStat_StatRelFileNodeEntry *
+pgstat_fetch_stat_relfilenodeentry(Oid dboid, Oid spcOid, RelFileNumber relfile)
+{
+	return (PgStat_StatRelFileNodeEntry *)
+		pgstat_fetch_entry(PGSTAT_KIND_RELFILENODE, dboid, spcOid, relfile);
+}
+
 /*
  * More efficient version of pgstat_fetch_stat_tabentry(), allowing to specify
  * whether the to-be-accessed table is a shared relation or not.
@@ -468,7 +484,7 @@ pgstat_fetch_stat_tabentry_ext(bool shared, Oid reloid)
 	Oid			dboid = (shared ? InvalidOid : MyDatabaseId);
 
 	return (PgStat_StatTabEntry *)
-		pgstat_fetch_entry(PGSTAT_KIND_RELATION, dboid, reloid);
+		pgstat_fetch_entry(PGSTAT_KIND_RELATION, dboid, reloid, InvalidOid);
 }
 
 /*
@@ -491,10 +507,10 @@ find_tabstat_entry(Oid rel_id)
 	PgStat_TableStatus *tabentry = NULL;
 	PgStat_TableStatus *tablestatus = NULL;
 
-	entry_ref = pgstat_fetch_pending_entry(PGSTAT_KIND_RELATION, MyDatabaseId, rel_id);
+	entry_ref = pgstat_fetch_pending_entry(PGSTAT_KIND_RELATION, MyDatabaseId, rel_id, InvalidOid);
 	if (!entry_ref)
 	{
-		entry_ref = pgstat_fetch_pending_entry(PGSTAT_KIND_RELATION, InvalidOid, rel_id);
+		entry_ref = pgstat_fetch_pending_entry(PGSTAT_KIND_RELATION, InvalidOid, rel_id, InvalidOid);
 		if (!entry_ref)
 			return tablestatus;
 	}
@@ -881,6 +897,38 @@ pgstat_relation_flush_cb(PgStat_EntryRef *entry_ref, bool nowait)
 	return true;
 }
 
+/*
+ * Flush out pending stats for the relfilenode entry
+ *
+ * If nowait is true, this function returns false if lock could not
+ * immediately acquired, otherwise true is returned.
+ */
+bool
+pgstat_relfilenode_flush_cb(PgStat_EntryRef *entry_ref, bool nowait)
+{
+	PgStatShared_RelFileNode *sharedent;
+	PgStat_StatRelFileNodeEntry *pendingent;
+
+	pendingent = (PgStat_StatRelFileNodeEntry *) entry_ref->pending;
+	sharedent = (PgStatShared_RelFileNode *) entry_ref->shared_stats;
+
+	if (!pgstat_lock_entry(entry_ref, nowait))
+		return false;
+
+#define PGSTAT_ACCUM_RELFILENODECOUNT(item)      \
+		(sharedent)->stats.item += (pendingent)->item
+
+	PGSTAT_ACCUM_RELFILENODECOUNT(blocks_fetched);
+	PGSTAT_ACCUM_RELFILENODECOUNT(blocks_hit);
+	PGSTAT_ACCUM_RELFILENODECOUNT(blocks_written);
+
+	pgstat_unlock_entry(entry_ref);
+
+	memset(pendingent, 0, sizeof(*pendingent));
+
+	return true;
+}
+
 void
 pgstat_relation_delete_pending_cb(PgStat_EntryRef *entry_ref)
 {
@@ -902,7 +950,7 @@ pgstat_prep_relation_pending(Oid rel_id, bool isshared)
 
 	entry_ref = pgstat_prep_pending_entry(PGSTAT_KIND_RELATION,
 										  isshared ? InvalidOid : MyDatabaseId,
-										  rel_id, NULL);
+										  rel_id, InvalidOid, NULL);
 	pending = entry_ref->pending;
 	pending->id = rel_id;
 	pending->shared = isshared;
@@ -910,6 +958,56 @@ pgstat_prep_relation_pending(Oid rel_id, bool isshared)
 	return pending;
 }
 
+PgStat_StatRelFileNodeEntry *
+pgstat_prep_relfilenode_pending(RelFileLocator locator)
+{
+	PgStat_EntryRef *entry_ref;
+
+	entry_ref = pgstat_prep_pending_entry(PGSTAT_KIND_RELFILENODE, locator.dbOid,
+										  locator.spcOid, locator.relNumber, NULL);
+
+	return entry_ref->pending;
+}
+
+void
+pgstat_report_relfilenode_blks_written(RelFileLocator locator)
+{
+	PgStat_StatRelFileNodeEntry *relfileentry = NULL;
+
+	relfileentry = pgstat_prep_relfilenode_pending(locator);
+
+	if (relfileentry)
+		relfileentry->blocks_written++;
+}
+
+void
+pgstat_report_relfilenode_buffer_read(Relation reln)
+{
+	PgStat_StatRelFileNodeEntry *relfileentry = NULL;
+
+	/* For relation stats to survive after a rewrite */
+	pgstat_count_buffer_read(reln);
+
+	relfileentry = pgstat_prep_relfilenode_pending(reln->rd_locator);
+
+	if (relfileentry)
+		relfileentry->blocks_fetched++;
+}
+
+void
+pgstat_report_relfilenode_buffer_hit(Relation reln)
+{
+	PgStat_StatRelFileNodeEntry *relfileentry = NULL;
+
+	/* For relation stats to survive after a rewrite */
+	pgstat_count_buffer_hit(reln);
+
+	relfileentry = pgstat_prep_relfilenode_pending(reln->rd_locator);
+
+	if (relfileentry)
+		relfileentry->blocks_hit++;
+}
+
 /*
  * add a new (sub)transaction state record
  */
diff --git a/src/backend/utils/activity/pgstat_replslot.c b/src/backend/utils/activity/pgstat_replslot.c
index 889e86ac5a..96c6621477 100644
--- a/src/backend/utils/activity/pgstat_replslot.c
+++ b/src/backend/utils/activity/pgstat_replslot.c
@@ -62,7 +62,7 @@ pgstat_reset_replslot(const char *name)
 	 */
 	if (SlotIsLogical(slot))
 		pgstat_reset(PGSTAT_KIND_REPLSLOT, InvalidOid,
-					 ReplicationSlotIndex(slot));
+					 ReplicationSlotIndex(slot), InvalidOid);
 
 	LWLockRelease(ReplicationSlotControlLock);
 }
@@ -82,7 +82,7 @@ pgstat_report_replslot(ReplicationSlot *slot, const PgStat_StatReplSlotEntry *re
 	PgStat_StatReplSlotEntry *statent;
 
 	entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_REPLSLOT, InvalidOid,
-											ReplicationSlotIndex(slot), false);
+											ReplicationSlotIndex(slot), InvalidOid, false);
 	shstatent = (PgStatShared_ReplSlot *) entry_ref->shared_stats;
 	statent = &shstatent->stats;
 
@@ -116,7 +116,7 @@ pgstat_create_replslot(ReplicationSlot *slot)
 	Assert(LWLockHeldByMeInMode(ReplicationSlotAllocationLock, LW_EXCLUSIVE));
 
 	entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_REPLSLOT, InvalidOid,
-											ReplicationSlotIndex(slot), false);
+											ReplicationSlotIndex(slot), InvalidOid, false);
 	shstatent = (PgStatShared_ReplSlot *) entry_ref->shared_stats;
 
 	/*
@@ -146,7 +146,7 @@ void
 pgstat_acquire_replslot(ReplicationSlot *slot)
 {
 	pgstat_get_entry_ref(PGSTAT_KIND_REPLSLOT, InvalidOid,
-						 ReplicationSlotIndex(slot), true, NULL);
+						 ReplicationSlotIndex(slot), InvalidOid, true, NULL);
 }
 
 /*
@@ -158,7 +158,7 @@ pgstat_drop_replslot(ReplicationSlot *slot)
 	Assert(LWLockHeldByMeInMode(ReplicationSlotAllocationLock, LW_EXCLUSIVE));
 
 	pgstat_drop_entry(PGSTAT_KIND_REPLSLOT, InvalidOid,
-					  ReplicationSlotIndex(slot));
+					  ReplicationSlotIndex(slot), InvalidOid);
 }
 
 /*
@@ -177,7 +177,7 @@ pgstat_fetch_replslot(NameData slotname)
 
 	if (idx != -1)
 		slotentry = (PgStat_StatReplSlotEntry *) pgstat_fetch_entry(PGSTAT_KIND_REPLSLOT,
-																	InvalidOid, idx);
+																	InvalidOid, idx, InvalidOid);
 
 	LWLockRelease(ReplicationSlotControlLock);
 
@@ -209,6 +209,7 @@ pgstat_replslot_from_serialized_name_cb(const NameData *name, PgStat_HashKey *ke
 	key->kind = PGSTAT_KIND_REPLSLOT;
 	key->dboid = InvalidOid;
 	key->objoid = idx;
+	key->relfile = InvalidOid;
 
 	return true;
 }
diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c
index 91591da395..d74b07e414 100644
--- a/src/backend/utils/activity/pgstat_shmem.c
+++ b/src/backend/utils/activity/pgstat_shmem.c
@@ -395,10 +395,10 @@ pgstat_get_entry_ref_cached(PgStat_HashKey key, PgStat_EntryRef **entry_ref_p)
  * if the entry is newly created, false otherwise.
  */
 PgStat_EntryRef *
-pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, Oid objoid, bool create,
-					 bool *created_entry)
+pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, Oid objoid, RelFileNumber relfile,
+					 bool create, bool *created_entry)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objoid = objoid};
+	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objoid = objoid,.relfile = relfile};
 	PgStatShared_HashEntry *shhashent;
 	PgStatShared_Common *shheader = NULL;
 	PgStat_EntryRef *entry_ref;
@@ -611,12 +611,12 @@ pgstat_unlock_entry(PgStat_EntryRef *entry_ref)
  */
 PgStat_EntryRef *
 pgstat_get_entry_ref_locked(PgStat_Kind kind, Oid dboid, Oid objoid,
-							bool nowait)
+							RelFileNumber relfile, bool nowait)
 {
 	PgStat_EntryRef *entry_ref;
 
 	/* find shared table stats entry corresponding to the local entry */
-	entry_ref = pgstat_get_entry_ref(kind, dboid, objoid, true, NULL);
+	entry_ref = pgstat_get_entry_ref(kind, dboid, objoid, relfile, true, NULL);
 
 	/* lock the shared entry to protect the content, skip if failed */
 	if (!pgstat_lock_entry(entry_ref, nowait))
@@ -856,9 +856,9 @@ pgstat_drop_database_and_contents(Oid dboid)
 }
 
 bool
-pgstat_drop_entry(PgStat_Kind kind, Oid dboid, Oid objoid)
+pgstat_drop_entry(PgStat_Kind kind, Oid dboid, Oid objoid, RelFileNumber relfile)
 {
-	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objoid = objoid};
+	PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objoid = objoid,.relfile = relfile};
 	PgStatShared_HashEntry *shent;
 	bool		freed = true;
 
@@ -931,13 +931,12 @@ shared_stat_reset_contents(PgStat_Kind kind, PgStatShared_Common *header,
  * Reset one variable-numbered stats entry.
  */
 void
-pgstat_reset_entry(PgStat_Kind kind, Oid dboid, Oid objoid, TimestampTz ts)
+pgstat_reset_entry(PgStat_Kind kind, Oid dboid, Oid objoid, RelFileNumber relfile, TimestampTz ts)
 {
 	PgStat_EntryRef *entry_ref;
 
 	Assert(!pgstat_get_kind_info(kind)->fixed_amount);
-
-	entry_ref = pgstat_get_entry_ref(kind, dboid, objoid, false, NULL);
+	entry_ref = pgstat_get_entry_ref(kind, dboid, objoid, relfile, false, NULL);
 	if (!entry_ref || entry_ref->shared_entry->dropped)
 		return;
 
diff --git a/src/backend/utils/activity/pgstat_subscription.c b/src/backend/utils/activity/pgstat_subscription.c
index d9af8de658..9b9ab2861b 100644
--- a/src/backend/utils/activity/pgstat_subscription.c
+++ b/src/backend/utils/activity/pgstat_subscription.c
@@ -30,7 +30,7 @@ pgstat_report_subscription_error(Oid subid, bool is_apply_error)
 	PgStat_BackendSubEntry *pending;
 
 	entry_ref = pgstat_prep_pending_entry(PGSTAT_KIND_SUBSCRIPTION,
-										  InvalidOid, subid, NULL);
+										  InvalidOid, subid, InvalidOid, NULL);
 	pending = entry_ref->pending;
 
 	if (is_apply_error)
@@ -47,12 +47,12 @@ pgstat_create_subscription(Oid subid)
 {
 	/* Ensures that stats are dropped if transaction rolls back */
 	pgstat_create_transactional(PGSTAT_KIND_SUBSCRIPTION,
-								InvalidOid, subid);
+								InvalidOid, subid, InvalidOid);
 
 	/* Create and initialize the subscription stats entry */
-	pgstat_get_entry_ref(PGSTAT_KIND_SUBSCRIPTION, InvalidOid, subid,
+	pgstat_get_entry_ref(PGSTAT_KIND_SUBSCRIPTION, InvalidOid, subid, InvalidOid,
 						 true, NULL);
-	pgstat_reset_entry(PGSTAT_KIND_SUBSCRIPTION, InvalidOid, subid, 0);
+	pgstat_reset_entry(PGSTAT_KIND_SUBSCRIPTION, InvalidOid, subid, InvalidOid, 0);
 }
 
 /*
@@ -64,7 +64,7 @@ void
 pgstat_drop_subscription(Oid subid)
 {
 	pgstat_drop_transactional(PGSTAT_KIND_SUBSCRIPTION,
-							  InvalidOid, subid);
+							  InvalidOid, subid, InvalidOid);
 }
 
 /*
@@ -75,7 +75,7 @@ PgStat_StatSubEntry *
 pgstat_fetch_stat_subscription(Oid subid)
 {
 	return (PgStat_StatSubEntry *)
-		pgstat_fetch_entry(PGSTAT_KIND_SUBSCRIPTION, InvalidOid, subid);
+		pgstat_fetch_entry(PGSTAT_KIND_SUBSCRIPTION, InvalidOid, subid, InvalidOid);
 }
 
 /*
diff --git a/src/backend/utils/activity/pgstat_xact.c b/src/backend/utils/activity/pgstat_xact.c
index 1877d22f14..b25df5112b 100644
--- a/src/backend/utils/activity/pgstat_xact.c
+++ b/src/backend/utils/activity/pgstat_xact.c
@@ -30,7 +30,7 @@ static void AtEOXact_PgStat_DroppedStats(PgStat_SubXactStatus *xact_state, bool
 static void AtEOSubXact_PgStat_DroppedStats(PgStat_SubXactStatus *xact_state,
 											bool isCommit, int nestDepth);
 
-static PgStat_SubXactStatus *pgStatXactStack = NULL;
+PgStat_SubXactStatus *pgStatXactStack = NULL;
 
 
 /*
@@ -84,7 +84,7 @@ AtEOXact_PgStat_DroppedStats(PgStat_SubXactStatus *xact_state, bool isCommit)
 			 * Transaction that dropped an object committed. Drop the stats
 			 * too.
 			 */
-			if (!pgstat_drop_entry(it->kind, it->dboid, it->objoid))
+			if (!pgstat_drop_entry(it->kind, it->dboid, it->objoid, it->relfile))
 				not_freed_count++;
 		}
 		else if (!isCommit && pending->is_create)
@@ -93,7 +93,7 @@ AtEOXact_PgStat_DroppedStats(PgStat_SubXactStatus *xact_state, bool isCommit)
 			 * Transaction that created an object aborted. Drop the stats
 			 * associated with the object.
 			 */
-			if (!pgstat_drop_entry(it->kind, it->dboid, it->objoid))
+			if (!pgstat_drop_entry(it->kind, it->dboid, it->objoid, it->relfile))
 				not_freed_count++;
 		}
 
@@ -105,6 +105,33 @@ AtEOXact_PgStat_DroppedStats(PgStat_SubXactStatus *xact_state, bool isCommit)
 		pgstat_request_entry_refs_gc();
 }
 
+/*
+ * Remove a relfilenode stat from the list of stats to be dropped.
+ */
+void
+PgStat_RemoveRelFileNodeFromDroppedStats(PgStat_SubXactStatus *xact_state, RelFileLocator rlocator)
+{
+	dlist_mutable_iter iter;
+
+	if (dclist_count(&xact_state->pending_drops) == 0)
+		return;
+
+	dclist_foreach_modify(iter, &xact_state->pending_drops)
+	{
+		PgStat_PendingDroppedStatsItem *pending =
+			dclist_container(PgStat_PendingDroppedStatsItem, node, iter.cur);
+		xl_xact_stats_item *it = &pending->item;
+
+		if (it->kind == PGSTAT_KIND_RELFILENODE && it->dboid == rlocator.dbOid
+			&& it->objoid == rlocator.spcOid && it->relfile == rlocator.relNumber)
+		{
+			dclist_delete_from(&xact_state->pending_drops, &pending->node);
+			pfree(pending);
+			return;
+		}
+	}
+}
+
 /*
  * Called from access/transam/xact.c at subtransaction commit/abort.
  */
@@ -158,7 +185,7 @@ AtEOSubXact_PgStat_DroppedStats(PgStat_SubXactStatus *xact_state,
 			 * Subtransaction creating a new stats object aborted. Drop the
 			 * stats object.
 			 */
-			if (!pgstat_drop_entry(it->kind, it->dboid, it->objoid))
+			if (!pgstat_drop_entry(it->kind, it->dboid, it->objoid, it->relfile))
 				not_freed_count++;
 			pfree(pending);
 		}
@@ -320,7 +347,11 @@ pgstat_execute_transactional_drops(int ndrops, struct xl_xact_stats_item *items,
 	{
 		xl_xact_stats_item *it = &items[i];
 
-		if (!pgstat_drop_entry(it->kind, it->dboid, it->objoid))
+		/* leave it to pgstat_drop_transactional() in RelationDropStorage() */
+		if (it->kind == PGSTAT_KIND_RELFILENODE)
+			continue;
+
+		if (!pgstat_drop_entry(it->kind, it->dboid, it->objoid, it->relfile))
 			not_freed_count++;
 	}
 
@@ -329,7 +360,7 @@ pgstat_execute_transactional_drops(int ndrops, struct xl_xact_stats_item *items,
 }
 
 static void
-create_drop_transactional_internal(PgStat_Kind kind, Oid dboid, Oid objoid, bool is_create)
+create_drop_transactional_internal(PgStat_Kind kind, Oid dboid, Oid objoid, RelFileNumber relfile, bool is_create)
 {
 	int			nest_level = GetCurrentTransactionNestLevel();
 	PgStat_SubXactStatus *xact_state;
@@ -342,6 +373,7 @@ create_drop_transactional_internal(PgStat_Kind kind, Oid dboid, Oid objoid, bool
 	drop->item.kind = kind;
 	drop->item.dboid = dboid;
 	drop->item.objoid = objoid;
+	drop->item.relfile = relfile;
 
 	dclist_push_tail(&xact_state->pending_drops, &drop->node);
 }
@@ -354,18 +386,18 @@ create_drop_transactional_internal(PgStat_Kind kind, Oid dboid, Oid objoid, bool
  * dropped.
  */
 void
-pgstat_create_transactional(PgStat_Kind kind, Oid dboid, Oid objoid)
+pgstat_create_transactional(PgStat_Kind kind, Oid dboid, Oid objoid, RelFileNumber relfile)
 {
-	if (pgstat_get_entry_ref(kind, dboid, objoid, false, NULL))
+	if (pgstat_get_entry_ref(kind, dboid, objoid, relfile, false, NULL))
 	{
 		ereport(WARNING,
-				errmsg("resetting existing statistics for kind %s, db=%u, oid=%u",
-					   (pgstat_get_kind_info(kind))->name, dboid, objoid));
+				errmsg("resetting existing statistics for kind %s, db=%u, oid=%u, relfile=%u",
+					   (pgstat_get_kind_info(kind))->name, dboid, objoid, relfile));
 
-		pgstat_reset(kind, dboid, objoid);
+		pgstat_reset(kind, dboid, objoid, relfile);
 	}
 
-	create_drop_transactional_internal(kind, dboid, objoid, /* create */ true);
+	create_drop_transactional_internal(kind, dboid, objoid, relfile, /* create */ true);
 }
 
 /*
@@ -376,7 +408,7 @@ pgstat_create_transactional(PgStat_Kind kind, Oid dboid, Oid objoid)
  * alive.
  */
 void
-pgstat_drop_transactional(PgStat_Kind kind, Oid dboid, Oid objoid)
+pgstat_drop_transactional(PgStat_Kind kind, Oid dboid, Oid objoid, RelFileNumber relfile)
 {
-	create_drop_transactional_internal(kind, dboid, objoid, /* create */ false);
+	create_drop_transactional_internal(kind, dboid, objoid, relfile, /* create */ false);
 }
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 3876339ee1..e266d96f5e 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -106,6 +106,30 @@ PG_STAT_GET_RELENTRY_INT64(tuples_updated)
 /* pg_stat_get_vacuum_count */
 PG_STAT_GET_RELENTRY_INT64(vacuum_count)
 
+#define PG_STAT_GET_RELFILEENTRY_INT64(stat)						\
+Datum															\
+CppConcat(pg_stat_get_relfilenode_,stat)(PG_FUNCTION_ARGS)					\
+{																\
+	Oid			dboid = PG_GETARG_OID(0);						\
+	Oid			 spcOid = PG_GETARG_OID(1);						\
+	RelFileNumber			 relfile = PG_GETARG_OID(2);						\
+	int64		result;											\
+	PgStat_StatRelFileNodeEntry *relfileentry;								\
+																\
+	if ((relfileentry = pgstat_fetch_stat_relfilenodeentry(dboid, spcOid, relfile)) == NULL)	\
+		result = 0;												\
+	else														\
+		result = (int64) (relfileentry->stat);						\
+																\
+	PG_RETURN_INT64(result);									\
+}
+
+/* pg_stat_get_relfilenode_blocks_written */
+PG_STAT_GET_RELFILEENTRY_INT64(blocks_written)
+
+/* pg_stat_get_blocks_written */
+PG_STAT_GET_RELENTRY_INT64(blocks_written)
+
 #define PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat)					\
 Datum															\
 CppConcat(pg_stat_get_,stat)(PG_FUNCTION_ARGS)					\
@@ -1752,7 +1776,7 @@ pg_stat_reset_single_table_counters(PG_FUNCTION_ARGS)
 	Oid			taboid = PG_GETARG_OID(0);
 	Oid			dboid = (IsSharedRelation(taboid) ? InvalidOid : MyDatabaseId);
 
-	pgstat_reset(PGSTAT_KIND_RELATION, dboid, taboid);
+	pgstat_reset(PGSTAT_KIND_RELATION, dboid, taboid, InvalidOid);
 
 	PG_RETURN_VOID();
 }
@@ -1762,7 +1786,7 @@ pg_stat_reset_single_function_counters(PG_FUNCTION_ARGS)
 {
 	Oid			funcoid = PG_GETARG_OID(0);
 
-	pgstat_reset(PGSTAT_KIND_FUNCTION, MyDatabaseId, funcoid);
+	pgstat_reset(PGSTAT_KIND_FUNCTION, MyDatabaseId, funcoid, InvalidOid);
 
 	PG_RETURN_VOID();
 }
@@ -1820,7 +1844,7 @@ pg_stat_reset_subscription_stats(PG_FUNCTION_ARGS)
 			ereport(ERROR,
 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 					 errmsg("invalid subscription OID %u", subid)));
-		pgstat_reset(PGSTAT_KIND_SUBSCRIPTION, InvalidOid, subid);
+		pgstat_reset(PGSTAT_KIND_SUBSCRIPTION, InvalidOid, subid, InvalidOid);
 	}
 
 	PG_RETURN_VOID();
@@ -2028,7 +2052,9 @@ pg_stat_have_stats(PG_FUNCTION_ARGS)
 	char	   *stats_type = text_to_cstring(PG_GETARG_TEXT_P(0));
 	Oid			dboid = PG_GETARG_OID(1);
 	Oid			objoid = PG_GETARG_OID(2);
+	Oid			relfile = PG_GETARG_OID(3);
+
 	PgStat_Kind kind = pgstat_get_kind_from_str(stats_type);
 
-	PG_RETURN_BOOL(pgstat_have_entry(kind, dboid, objoid));
+	PG_RETURN_BOOL(pgstat_have_entry(kind, dboid, objoid, relfile));
 }
diff --git a/src/include/access/tableam.h b/src/include/access/tableam.h
index 8e583b45cd..792cd2237e 100644
--- a/src/include/access/tableam.h
+++ b/src/include/access/tableam.h
@@ -21,7 +21,9 @@
 #include "access/sdir.h"
 #include "access/xact.h"
 #include "executor/tuptable.h"
+#include "pgstat.h"
 #include "storage/read_stream.h"
+#include "utils/pgstat_internal.h"
 #include "utils/rel.h"
 #include "utils/snapshot.h"
 
@@ -1634,6 +1636,23 @@ table_relation_set_new_filelocator(Relation rel,
 								   TransactionId *freezeXid,
 								   MultiXactId *minmulti)
 {
+	PgStat_StatRelFileNodeEntry *relfileentry;
+	PgStat_StatTabEntry *tabentry = NULL;
+	PgStat_EntryRef *entry_ref = NULL;
+	PgStatShared_Relation *shtabentry;
+
+	entry_ref = pgstat_get_entry_ref(PGSTAT_KIND_RELATION, MyDatabaseId, rel->rd_id, InvalidOid, false, NULL);
+	if (entry_ref)
+	{
+		shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats;
+		tabentry = &shtabentry->stats;
+	}
+
+	relfileentry = pgstat_fetch_stat_relfilenodeentry(rel->rd_locator.dbOid, rel->rd_locator.spcOid, rel->rd_locator.relNumber);
+
+	if (tabentry && relfileentry)
+		tabentry->blocks_written += relfileentry->blocks_written;
+
 	rel->rd_tableam->relation_set_new_filelocator(rel, newrlocator,
 												  persistence, freezeXid,
 												  minmulti);
diff --git a/src/include/access/xact.h b/src/include/access/xact.h
index 6d4439f052..3b9ed65ff6 100644
--- a/src/include/access/xact.h
+++ b/src/include/access/xact.h
@@ -284,6 +284,7 @@ typedef struct xl_xact_stats_item
 	int			kind;
 	Oid			dboid;
 	Oid			objoid;
+	RelFileNumber relfile;
 } xl_xact_stats_item;
 
 typedef struct xl_xact_stats_items
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 6a5476d3c4..912471a1ac 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5374,6 +5374,14 @@
   proname => 'pg_stat_get_tuples_updated', provolatile => 's',
   proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
   prosrc => 'pg_stat_get_tuples_updated' },
+{ oid => '9280', descr => 'statistics: number of blocks written',
+  proname => 'pg_stat_get_relfilenode_blocks_written', provolatile => 's',
+  proparallel => 'r',
+  proargtypes => 'oid oid oid',
+  prorettype => 'int8',
+  proallargtypes => '{oid,oid,oid,int8}',
+  proargmodes => '{i,i,i,o}',
+  prosrc => 'pg_stat_get_relfilenode_blocks_written' },
 { oid => '1933', descr => 'statistics: number of tuples deleted',
   proname => 'pg_stat_get_tuples_deleted', provolatile => 's',
   proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
@@ -5413,6 +5421,10 @@
   proname => 'pg_stat_get_blocks_hit', provolatile => 's', proparallel => 'r',
   prorettype => 'int8', proargtypes => 'oid',
   prosrc => 'pg_stat_get_blocks_hit' },
+{ oid => '8438', descr => 'statistics: number of blocks written',
+  proname => 'pg_stat_get_blocks_written', provolatile => 's', proparallel => 'r',
+  prorettype => 'int8', proargtypes => 'oid',
+  prosrc => 'pg_stat_get_blocks_written' },
 { 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',
@@ -5499,7 +5511,7 @@
 
 { oid => '6230', descr => 'statistics: check if a stats object exists',
   proname => 'pg_stat_have_stats', provolatile => 'v', proparallel => 'r',
-  prorettype => 'bool', proargtypes => 'text oid oid',
+  prorettype => 'bool', proargtypes => 'text oid oid oid',
   prosrc => 'pg_stat_have_stats' },
 
 { oid => '6231', descr => 'statistics: information about subscription stats',
diff --git a/src/include/pgstat.h b/src/include/pgstat.h
index 2136239710..9631689430 100644
--- a/src/include/pgstat.h
+++ b/src/include/pgstat.h
@@ -14,6 +14,7 @@
 #include "datatype/timestamp.h"
 #include "portability/instr_time.h"
 #include "postmaster/pgarch.h"	/* for MAX_XFN_CHARS */
+#include "storage/relfilelocator.h"
 #include "utils/backend_progress.h" /* for backward compatibility */
 #include "utils/backend_status.h"	/* for backward compatibility */
 #include "utils/relcache.h"
@@ -40,6 +41,7 @@ typedef enum PgStat_Kind
 	/* stats for variable-numbered objects */
 	PGSTAT_KIND_DATABASE,		/* database-wide statistics */
 	PGSTAT_KIND_RELATION,		/* per-table statistics */
+	PGSTAT_KIND_RELFILENODE,	/* per-relfilenode statistics */
 	PGSTAT_KIND_FUNCTION,		/* per-function statistics */
 	PGSTAT_KIND_REPLSLOT,		/* per-slot statistics */
 	PGSTAT_KIND_SUBSCRIPTION,	/* per-subscription statistics */
@@ -417,6 +419,7 @@ typedef struct PgStat_StatTabEntry
 
 	PgStat_Counter blocks_fetched;
 	PgStat_Counter blocks_hit;
+	PgStat_Counter blocks_written;
 
 	TimestampTz last_vacuum_time;	/* user initiated vacuum */
 	PgStat_Counter vacuum_count;
@@ -428,6 +431,13 @@ typedef struct PgStat_StatTabEntry
 	PgStat_Counter autoanalyze_count;
 } PgStat_StatTabEntry;
 
+typedef struct PgStat_StatRelFileNodeEntry
+{
+	PgStat_Counter blocks_fetched;
+	PgStat_Counter blocks_hit;
+	PgStat_Counter blocks_written;
+} PgStat_StatRelFileNodeEntry;
+
 typedef struct PgStat_WalStats
 {
 	PgStat_Counter wal_records;
@@ -478,7 +488,7 @@ extern long pgstat_report_stat(bool force);
 extern void pgstat_force_next_flush(void);
 
 extern void pgstat_reset_counters(void);
-extern void pgstat_reset(PgStat_Kind kind, Oid dboid, Oid objoid);
+extern void pgstat_reset(PgStat_Kind kind, Oid dboid, Oid objoid, RelFileNumber relfile);
 extern void pgstat_reset_of_kind(PgStat_Kind kind);
 
 /* stats accessors */
@@ -487,7 +497,7 @@ extern TimestampTz pgstat_get_stat_snapshot_timestamp(bool *have_snapshot);
 
 /* helpers */
 extern PgStat_Kind pgstat_get_kind_from_str(char *kind_str);
-extern bool pgstat_have_entry(PgStat_Kind kind, Oid dboid, Oid objoid);
+extern bool pgstat_have_entry(PgStat_Kind kind, Oid dboid, Oid objoid, RelFileNumber relfile);
 
 
 /*
@@ -596,6 +606,10 @@ extern void pgstat_report_analyze(Relation rel,
 								  PgStat_Counter livetuples, PgStat_Counter deadtuples,
 								  bool resetcounter);
 
+extern void pgstat_report_relfilenode_blks_written(RelFileLocator locator);
+extern void pgstat_report_relfilenode_buffer_read(Relation reln);
+extern void pgstat_report_relfilenode_buffer_hit(Relation reln);
+
 /*
  * If stats are enabled, but pending data hasn't been prepared yet, call
  * pgstat_assoc_relation() to do so. See its comment for why this is done
@@ -655,6 +669,7 @@ extern void pgstat_twophase_postabort(TransactionId xid, uint16 info,
 									  void *recdata, uint32 len);
 
 extern PgStat_StatTabEntry *pgstat_fetch_stat_tabentry(Oid relid);
+extern PgStat_StatRelFileNodeEntry *pgstat_fetch_stat_relfilenodeentry(Oid dboid, Oid spcOid, RelFileNumber relfile);
 extern PgStat_StatTabEntry *pgstat_fetch_stat_tabentry_ext(bool shared,
 														   Oid reloid);
 extern PgStat_TableStatus *find_tabstat_entry(Oid rel_id);
diff --git a/src/include/utils/pgstat_internal.h b/src/include/utils/pgstat_internal.h
index dbbca31602..50d5f1a577 100644
--- a/src/include/utils/pgstat_internal.h
+++ b/src/include/utils/pgstat_internal.h
@@ -53,7 +53,8 @@ typedef struct PgStat_HashKey
 {
 	PgStat_Kind kind;			/* statistics entry kind */
 	Oid			dboid;			/* database ID. InvalidOid for shared objects. */
-	Oid			objoid;			/* object ID, either table or function. */
+	Oid			objoid;			/* object ID, either table or function or tablespace. */
+	RelFileNumber relfile;		/* relfilenumber for RelFileLocator. */
 } PgStat_HashKey;
 
 /*
@@ -376,6 +377,12 @@ typedef struct PgStatShared_Relation
 	PgStat_StatTabEntry stats;
 } PgStatShared_Relation;
 
+typedef struct PgStatShared_RelFileNode
+{
+	PgStatShared_Common header;
+	PgStat_StatRelFileNodeEntry stats;
+} PgStatShared_RelFileNode;
+
 typedef struct PgStatShared_Function
 {
 	PgStatShared_Common header;
@@ -498,6 +505,9 @@ static inline size_t pgstat_get_entry_len(PgStat_Kind kind);
 static inline void *pgstat_get_entry_data(PgStat_Kind kind, PgStatShared_Common *entry);
 
 
+extern PgStat_SubXactStatus *pgStatXactStack;
+extern void PgStat_RemoveRelFileNodeFromDroppedStats(PgStat_SubXactStatus *xact_state, RelFileLocator rlocator);
+
 /*
  * Functions in pgstat.c
  */
@@ -511,10 +521,12 @@ extern void pgstat_assert_is_up(void);
 #endif
 
 extern void pgstat_delete_pending_entry(PgStat_EntryRef *entry_ref);
-extern PgStat_EntryRef *pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, Oid objoid, bool *created_entry);
-extern PgStat_EntryRef *pgstat_fetch_pending_entry(PgStat_Kind kind, Oid dboid, Oid objoid);
+extern PgStat_EntryRef *pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid,
+												  Oid objoid, RelFileNumber relfile,
+												  bool *created_entry);
+extern PgStat_EntryRef *pgstat_fetch_pending_entry(PgStat_Kind kind, Oid dboid, Oid objoid, RelFileNumber relfile);
 
-extern void *pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, Oid objoid);
+extern void *pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, Oid objoid, RelFileNumber relfile);
 extern void pgstat_snapshot_fixed(PgStat_Kind kind);
 
 
@@ -582,6 +594,7 @@ extern void AtPrepare_PgStat_Relations(PgStat_SubXactStatus *xact_state);
 extern void PostPrepare_PgStat_Relations(PgStat_SubXactStatus *xact_state);
 
 extern bool pgstat_relation_flush_cb(PgStat_EntryRef *entry_ref, bool nowait);
+extern bool pgstat_relfilenode_flush_cb(PgStat_EntryRef *entry_ref, bool nowait);
 extern void pgstat_relation_delete_pending_cb(PgStat_EntryRef *entry_ref);
 
 
@@ -602,15 +615,16 @@ extern void pgstat_attach_shmem(void);
 extern void pgstat_detach_shmem(void);
 
 extern PgStat_EntryRef *pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, Oid objoid,
-											 bool create, bool *created_entry);
+											 RelFileNumber relfile, bool create,
+											 bool *created_entry);
 extern bool pgstat_lock_entry(PgStat_EntryRef *entry_ref, bool nowait);
 extern bool pgstat_lock_entry_shared(PgStat_EntryRef *entry_ref, bool nowait);
 extern void pgstat_unlock_entry(PgStat_EntryRef *entry_ref);
-extern bool pgstat_drop_entry(PgStat_Kind kind, Oid dboid, Oid objoid);
+extern bool pgstat_drop_entry(PgStat_Kind kind, Oid dboid, Oid objoid, RelFileNumber relfile);
 extern void pgstat_drop_all_entries(void);
 extern PgStat_EntryRef *pgstat_get_entry_ref_locked(PgStat_Kind kind, Oid dboid, Oid objoid,
-													bool nowait);
-extern void pgstat_reset_entry(PgStat_Kind kind, Oid dboid, Oid objoid, TimestampTz ts);
+													RelFileNumber relfile, bool nowait);
+extern void pgstat_reset_entry(PgStat_Kind kind, Oid dboid, Oid objoid, RelFileNumber relfile, TimestampTz ts);
 extern void pgstat_reset_entries_of_kind(PgStat_Kind kind, TimestampTz ts);
 extern void pgstat_reset_matching_entries(bool (*do_reset) (PgStatShared_HashEntry *, Datum),
 										  Datum match_data,
@@ -655,8 +669,8 @@ extern void pgstat_subscription_reset_timestamp_cb(PgStatShared_Common *header,
  */
 
 extern PgStat_SubXactStatus *pgstat_get_xact_stack_level(int nest_level);
-extern void pgstat_drop_transactional(PgStat_Kind kind, Oid dboid, Oid objoid);
-extern void pgstat_create_transactional(PgStat_Kind kind, Oid dboid, Oid objoid);
+extern void pgstat_drop_transactional(PgStat_Kind kind, Oid dboid, Oid objoid, RelFileNumber relfile);
+extern void pgstat_create_transactional(PgStat_Kind kind, Oid dboid, Oid objoid, RelFileNumber relfile);
 
 
 /*
diff --git a/src/test/recovery/t/029_stats_restart.pl b/src/test/recovery/t/029_stats_restart.pl
index 6a1615a1e8..ee5a404b45 100644
--- a/src/test/recovery/t/029_stats_restart.pl
+++ b/src/test/recovery/t/029_stats_restart.pl
@@ -40,10 +40,10 @@ trigger_funcrel_stat();
 
 # verify stats objects exist
 my $sect = "initial";
-is(have_stats('database', $dboid, 0), 't', "$sect: db stats do exist");
-is(have_stats('function', $dboid, $funcoid),
+is(have_stats('database', $dboid, 0, 0), 't', "$sect: db stats do exist");
+is(have_stats('function', $dboid, $funcoid, 0),
 	't', "$sect: function stats do exist");
-is(have_stats('relation', $dboid, $tableoid),
+is(have_stats('relation', $dboid, $tableoid, 0),
 	't', "$sect: relation stats do exist");
 
 # regular shutdown
@@ -64,10 +64,10 @@ copy($og_stats, $statsfile) or die "Copy failed: $!";
 $node->start;
 
 $sect = "copy";
-is(have_stats('database', $dboid, 0), 't', "$sect: db stats do exist");
-is(have_stats('function', $dboid, $funcoid),
+is(have_stats('database', $dboid, 0, 0), 't', "$sect: db stats do exist");
+is(have_stats('function', $dboid, $funcoid, 0),
 	't', "$sect: function stats do exist");
-is(have_stats('relation', $dboid, $tableoid),
+is(have_stats('relation', $dboid, $tableoid, 0),
 	't', "$sect: relation stats do exist");
 
 $node->stop('immediate');
@@ -81,10 +81,10 @@ $node->start;
 
 # stats should have been discarded
 $sect = "post immediate";
-is(have_stats('database', $dboid, 0), 'f', "$sect: db stats do not exist");
-is(have_stats('function', $dboid, $funcoid),
+is(have_stats('database', $dboid, 0, 0), 'f', "$sect: db stats do not exist");
+is(have_stats('function', $dboid, $funcoid, 0),
 	'f', "$sect: function stats do exist");
-is(have_stats('relation', $dboid, $tableoid),
+is(have_stats('relation', $dboid, $tableoid, 0),
 	'f', "$sect: relation stats do not exist");
 
 # get rid of backup statsfile
@@ -95,10 +95,10 @@ unlink $statsfile or die "cannot unlink $statsfile $!";
 trigger_funcrel_stat();
 
 $sect = "post immediate, new";
-is(have_stats('database', $dboid, 0), 't', "$sect: db stats do exist");
-is(have_stats('function', $dboid, $funcoid),
+is(have_stats('database', $dboid, 0, 0), 't', "$sect: db stats do exist");
+is(have_stats('function', $dboid, $funcoid, 0),
 	't', "$sect: function stats do exist");
-is(have_stats('relation', $dboid, $tableoid),
+is(have_stats('relation', $dboid, $tableoid, 0),
 	't', "$sect: relation stats do exist");
 
 # regular shutdown
@@ -114,10 +114,10 @@ $node->start;
 
 # no stats present due to invalid stats file
 $sect = "invalid_overwrite";
-is(have_stats('database', $dboid, 0), 'f', "$sect: db stats do not exist");
-is(have_stats('function', $dboid, $funcoid),
+is(have_stats('database', $dboid, 0, 0), 'f', "$sect: db stats do not exist");
+is(have_stats('function', $dboid, $funcoid, 0),
 	'f', "$sect: function stats do not exist");
-is(have_stats('relation', $dboid, $tableoid),
+is(have_stats('relation', $dboid, $tableoid, 0),
 	'f', "$sect: relation stats do not exist");
 
 
@@ -130,10 +130,10 @@ append_file($og_stats, "XYZ");
 $node->start;
 
 $sect = "invalid_append";
-is(have_stats('database', $dboid, 0), 'f', "$sect: db stats do not exist");
-is(have_stats('function', $dboid, $funcoid),
+is(have_stats('database', $dboid, 0, 0), 'f', "$sect: db stats do not exist");
+is(have_stats('function', $dboid, $funcoid, 0),
 	'f', "$sect: function stats do not exist");
-is(have_stats('relation', $dboid, $tableoid),
+is(have_stats('relation', $dboid, $tableoid, 0),
 	'f', "$sect: relation stats do not exist");
 
 
@@ -292,10 +292,10 @@ sub trigger_funcrel_stat
 
 sub have_stats
 {
-	my ($kind, $dboid, $objoid) = @_;
+	my ($kind, $dboid, $objoid, $relfile) = @_;
 
 	return $node->safe_psql($connect_db,
-		"SELECT pg_stat_have_stats('$kind', $dboid, $objoid)");
+		"SELECT pg_stat_have_stats('$kind', $dboid, $objoid, $relfile)");
 }
 
 sub overwrite_file
diff --git a/src/test/recovery/t/030_stats_cleanup_replica.pl b/src/test/recovery/t/030_stats_cleanup_replica.pl
index 74b516cc7c..317df24c4f 100644
--- a/src/test/recovery/t/030_stats_cleanup_replica.pl
+++ b/src/test/recovery/t/030_stats_cleanup_replica.pl
@@ -179,9 +179,9 @@ sub test_standby_func_tab_stats_status
 	my %stats;
 
 	$stats{rel} = $node_standby->safe_psql($connect_db,
-		"SELECT pg_stat_have_stats('relation', $dboid, $tableoid)");
+		"SELECT pg_stat_have_stats('relation', $dboid, $tableoid, 0)");
 	$stats{func} = $node_standby->safe_psql($connect_db,
-		"SELECT pg_stat_have_stats('function', $dboid, $funcoid)");
+		"SELECT pg_stat_have_stats('function', $dboid, $funcoid, 0)");
 
 	is_deeply(\%stats, \%expected, "$sect: standby stats as expected");
 
@@ -194,7 +194,7 @@ sub test_standby_db_stats_status
 	my ($connect_db, $dboid, $present) = @_;
 
 	is( $node_standby->safe_psql(
-			$connect_db, "SELECT pg_stat_have_stats('database', $dboid, 0)"),
+			$connect_db, "SELECT pg_stat_have_stats('database', $dboid, 0, 0)"),
 		$present,
 		"$sect: standby db stats as expected");
 }
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index ef658ad740..a2fa165c4c 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -2335,6 +2335,11 @@ pg_statio_all_tables| SELECT c.oid AS relid,
     n.nspname AS schemaname,
     c.relname,
     (pg_stat_get_blocks_fetched(c.oid) - pg_stat_get_blocks_hit(c.oid)) AS heap_blks_read,
+    (pg_stat_get_blocks_written(c.oid) + pg_stat_get_relfilenode_blocks_written(d.oid,
+        CASE
+            WHEN (c.reltablespace <> (0)::oid) THEN c.reltablespace
+            ELSE d.dattablespace
+        END, c.relfilenode)) AS heap_blks_written,
     pg_stat_get_blocks_hit(c.oid) AS heap_blks_hit,
     i.idx_blks_read,
     i.idx_blks_hit,
@@ -2342,7 +2347,8 @@ pg_statio_all_tables| SELECT c.oid AS relid,
     pg_stat_get_blocks_hit(t.oid) AS toast_blks_hit,
     x.idx_blks_read AS tidx_blks_read,
     x.idx_blks_hit AS tidx_blks_hit
-   FROM ((((pg_class c
+   FROM pg_database d,
+    ((((pg_class c
      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,
@@ -2353,7 +2359,7 @@ pg_statio_all_tables| SELECT c.oid AS relid,
             (sum(pg_stat_get_blocks_hit(pg_index.indexrelid)))::bigint AS idx_blks_hit
            FROM pg_index
           WHERE (pg_index.indrelid = t.oid)) x ON (true))
-  WHERE (c.relkind = ANY (ARRAY['r'::"char", 't'::"char", 'm'::"char"]));
+  WHERE ((c.relkind = ANY (ARRAY['r'::"char", 't'::"char", 'm'::"char"])) AND (d.datname = current_database()));
 pg_statio_sys_indexes| SELECT relid,
     indexrelid,
     schemaname,
@@ -2374,6 +2380,7 @@ pg_statio_sys_tables| SELECT relid,
     schemaname,
     relname,
     heap_blks_read,
+    heap_blks_written,
     heap_blks_hit,
     idx_blks_read,
     idx_blks_hit,
@@ -2403,6 +2410,7 @@ pg_statio_user_tables| SELECT relid,
     schemaname,
     relname,
     heap_blks_read,
+    heap_blks_written,
     heap_blks_hit,
     idx_blks_read,
     idx_blks_hit,
diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out
index 6e08898b18..eff0c9372c 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -1111,23 +1111,23 @@ ROLLBACK;
 -- pg_stat_have_stats behavior
 ----
 -- fixed-numbered stats exist
-SELECT pg_stat_have_stats('bgwriter', 0, 0);
+SELECT pg_stat_have_stats('bgwriter', 0, 0, 0);
  pg_stat_have_stats 
 --------------------
  t
 (1 row)
 
 -- unknown stats kinds error out
-SELECT pg_stat_have_stats('zaphod', 0, 0);
+SELECT pg_stat_have_stats('zaphod', 0, 0, 0);
 ERROR:  invalid statistics kind: "zaphod"
 -- db stats have objoid 0
-SELECT pg_stat_have_stats('database', :dboid, 1);
+SELECT pg_stat_have_stats('database', :dboid, 1, 0);
  pg_stat_have_stats 
 --------------------
  f
 (1 row)
 
-SELECT pg_stat_have_stats('database', :dboid, 0);
+SELECT pg_stat_have_stats('database', :dboid, 0, 0);
  pg_stat_have_stats 
 --------------------
  t
@@ -1144,21 +1144,21 @@ select a from stats_test_tab1 where a = 3;
  3
 (1 row)
 
-SELECT pg_stat_have_stats('relation', :dboid, :stats_test_idx1_oid);
+SELECT pg_stat_have_stats('relation', :dboid, :stats_test_idx1_oid, 0);
  pg_stat_have_stats 
 --------------------
  t
 (1 row)
 
 -- pg_stat_have_stats returns false for dropped index with stats
-SELECT pg_stat_have_stats('relation', :dboid, :stats_test_idx1_oid);
+SELECT pg_stat_have_stats('relation', :dboid, :stats_test_idx1_oid, 0);
  pg_stat_have_stats 
 --------------------
  t
 (1 row)
 
 DROP index stats_test_idx1;
-SELECT pg_stat_have_stats('relation', :dboid, :stats_test_idx1_oid);
+SELECT pg_stat_have_stats('relation', :dboid, :stats_test_idx1_oid, 0);
  pg_stat_have_stats 
 --------------------
  f
@@ -1174,14 +1174,14 @@ select a from stats_test_tab1 where a = 3;
  3
 (1 row)
 
-SELECT pg_stat_have_stats('relation', :dboid, :stats_test_idx1_oid);
+SELECT pg_stat_have_stats('relation', :dboid, :stats_test_idx1_oid, 0);
  pg_stat_have_stats 
 --------------------
  t
 (1 row)
 
 ROLLBACK;
-SELECT pg_stat_have_stats('relation', :dboid, :stats_test_idx1_oid);
+SELECT pg_stat_have_stats('relation', :dboid, :stats_test_idx1_oid, 0);
  pg_stat_have_stats 
 --------------------
  f
@@ -1196,7 +1196,7 @@ select a from stats_test_tab1 where a = 3;
  3
 (1 row)
 
-SELECT pg_stat_have_stats('relation', :dboid, :stats_test_idx1_oid);
+SELECT pg_stat_have_stats('relation', :dboid, :stats_test_idx1_oid, 0);
  pg_stat_have_stats 
 --------------------
  t
@@ -1204,7 +1204,7 @@ SELECT pg_stat_have_stats('relation', :dboid, :stats_test_idx1_oid);
 
 REINDEX index CONCURRENTLY stats_test_idx1;
 -- false for previous oid
-SELECT pg_stat_have_stats('relation', :dboid, :stats_test_idx1_oid);
+SELECT pg_stat_have_stats('relation', :dboid, :stats_test_idx1_oid, 0);
  pg_stat_have_stats 
 --------------------
  f
@@ -1212,7 +1212,7 @@ SELECT pg_stat_have_stats('relation', :dboid, :stats_test_idx1_oid);
 
 -- true for new oid
 SELECT 'stats_test_idx1'::regclass::oid AS stats_test_idx1_oid \gset
-SELECT pg_stat_have_stats('relation', :dboid, :stats_test_idx1_oid);
+SELECT pg_stat_have_stats('relation', :dboid, :stats_test_idx1_oid, 0);
  pg_stat_have_stats 
 --------------------
  t
@@ -1220,7 +1220,7 @@ SELECT pg_stat_have_stats('relation', :dboid, :stats_test_idx1_oid);
 
 -- pg_stat_have_stats returns true for a rolled back drop index with stats
 BEGIN;
-SELECT pg_stat_have_stats('relation', :dboid, :stats_test_idx1_oid);
+SELECT pg_stat_have_stats('relation', :dboid, :stats_test_idx1_oid, 0);
  pg_stat_have_stats 
 --------------------
  t
@@ -1228,7 +1228,7 @@ SELECT pg_stat_have_stats('relation', :dboid, :stats_test_idx1_oid);
 
 DROP index stats_test_idx1;
 ROLLBACK;
-SELECT pg_stat_have_stats('relation', :dboid, :stats_test_idx1_oid);
+SELECT pg_stat_have_stats('relation', :dboid, :stats_test_idx1_oid, 0);
  pg_stat_have_stats 
 --------------------
  t
@@ -1513,7 +1513,7 @@ SELECT :io_sum_bulkwrite_strategy_extends_after > :io_sum_bulkwrite_strategy_ext
 (1 row)
 
 -- Test IO stats reset
-SELECT pg_stat_have_stats('io', 0, 0);
+SELECT pg_stat_have_stats('io', 0, 0, 0);
  pg_stat_have_stats 
 --------------------
  t
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index d8ac0d06f4..5a40779989 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -539,12 +539,12 @@ ROLLBACK;
 -- pg_stat_have_stats behavior
 ----
 -- fixed-numbered stats exist
-SELECT pg_stat_have_stats('bgwriter', 0, 0);
+SELECT pg_stat_have_stats('bgwriter', 0, 0, 0);
 -- unknown stats kinds error out
-SELECT pg_stat_have_stats('zaphod', 0, 0);
+SELECT pg_stat_have_stats('zaphod', 0, 0, 0);
 -- db stats have objoid 0
-SELECT pg_stat_have_stats('database', :dboid, 1);
-SELECT pg_stat_have_stats('database', :dboid, 0);
+SELECT pg_stat_have_stats('database', :dboid, 1, 0);
+SELECT pg_stat_have_stats('database', :dboid, 0, 0);
 
 -- pg_stat_have_stats returns true for committed index creation
 CREATE table stats_test_tab1 as select generate_series(1,10) a;
@@ -552,40 +552,40 @@ CREATE index stats_test_idx1 on stats_test_tab1(a);
 SELECT 'stats_test_idx1'::regclass::oid AS stats_test_idx1_oid \gset
 SET enable_seqscan TO off;
 select a from stats_test_tab1 where a = 3;
-SELECT pg_stat_have_stats('relation', :dboid, :stats_test_idx1_oid);
+SELECT pg_stat_have_stats('relation', :dboid, :stats_test_idx1_oid, 0);
 
 -- pg_stat_have_stats returns false for dropped index with stats
-SELECT pg_stat_have_stats('relation', :dboid, :stats_test_idx1_oid);
+SELECT pg_stat_have_stats('relation', :dboid, :stats_test_idx1_oid, 0);
 DROP index stats_test_idx1;
-SELECT pg_stat_have_stats('relation', :dboid, :stats_test_idx1_oid);
+SELECT pg_stat_have_stats('relation', :dboid, :stats_test_idx1_oid, 0);
 
 -- pg_stat_have_stats returns false for rolled back index creation
 BEGIN;
 CREATE index stats_test_idx1 on stats_test_tab1(a);
 SELECT 'stats_test_idx1'::regclass::oid AS stats_test_idx1_oid \gset
 select a from stats_test_tab1 where a = 3;
-SELECT pg_stat_have_stats('relation', :dboid, :stats_test_idx1_oid);
+SELECT pg_stat_have_stats('relation', :dboid, :stats_test_idx1_oid, 0);
 ROLLBACK;
-SELECT pg_stat_have_stats('relation', :dboid, :stats_test_idx1_oid);
+SELECT pg_stat_have_stats('relation', :dboid, :stats_test_idx1_oid, 0);
 
 -- pg_stat_have_stats returns true for reindex CONCURRENTLY
 CREATE index stats_test_idx1 on stats_test_tab1(a);
 SELECT 'stats_test_idx1'::regclass::oid AS stats_test_idx1_oid \gset
 select a from stats_test_tab1 where a = 3;
-SELECT pg_stat_have_stats('relation', :dboid, :stats_test_idx1_oid);
+SELECT pg_stat_have_stats('relation', :dboid, :stats_test_idx1_oid, 0);
 REINDEX index CONCURRENTLY stats_test_idx1;
 -- false for previous oid
-SELECT pg_stat_have_stats('relation', :dboid, :stats_test_idx1_oid);
+SELECT pg_stat_have_stats('relation', :dboid, :stats_test_idx1_oid, 0);
 -- true for new oid
 SELECT 'stats_test_idx1'::regclass::oid AS stats_test_idx1_oid \gset
-SELECT pg_stat_have_stats('relation', :dboid, :stats_test_idx1_oid);
+SELECT pg_stat_have_stats('relation', :dboid, :stats_test_idx1_oid, 0);
 
 -- pg_stat_have_stats returns true for a rolled back drop index with stats
 BEGIN;
-SELECT pg_stat_have_stats('relation', :dboid, :stats_test_idx1_oid);
+SELECT pg_stat_have_stats('relation', :dboid, :stats_test_idx1_oid, 0);
 DROP index stats_test_idx1;
 ROLLBACK;
-SELECT pg_stat_have_stats('relation', :dboid, :stats_test_idx1_oid);
+SELECT pg_stat_have_stats('relation', :dboid, :stats_test_idx1_oid, 0);
 
 -- put enable_seqscan back to on
 SET enable_seqscan TO on;
@@ -759,7 +759,7 @@ SELECT sum(extends) AS io_sum_bulkwrite_strategy_extends_after
 SELECT :io_sum_bulkwrite_strategy_extends_after > :io_sum_bulkwrite_strategy_extends_before;
 
 -- Test IO stats reset
-SELECT pg_stat_have_stats('io', 0, 0);
+SELECT pg_stat_have_stats('io', 0, 0, 0);
 SELECT sum(evictions) + sum(reuses) + sum(extends) + sum(fsyncs) + sum(reads) + sum(writes) + sum(writebacks) + sum(hits) AS io_stats_pre_reset
   FROM pg_stat_io \gset
 SELECT pg_stat_reset_shared('io');
diff --git a/src/test/subscription/t/026_stats.pl b/src/test/subscription/t/026_stats.pl
index fb3e5629b3..1f4ae5efd5 100644
--- a/src/test/subscription/t/026_stats.pl
+++ b/src/test/subscription/t/026_stats.pl
@@ -263,7 +263,7 @@ $node_subscriber->safe_psql($db, qq(DROP SUBSCRIPTION $sub1_name));
 
 # Subscription stats for sub1 should be gone
 is( $node_subscriber->safe_psql(
-		$db, qq(SELECT pg_stat_have_stats('subscription', 0, $sub1_oid))),
+		$db, qq(SELECT pg_stat_have_stats('subscription', 0, $sub1_oid, 0))),
 	qq(f),
 	qq(Subscription stats for subscription '$sub1_name' should be removed.));
 
@@ -282,7 +282,7 @@ DROP SUBSCRIPTION $sub2_name;
 
 # Subscription stats for sub2 should be gone
 is( $node_subscriber->safe_psql(
-		$db, qq(SELECT pg_stat_have_stats('subscription', 0, $sub2_oid))),
+		$db, qq(SELECT pg_stat_have_stats('subscription', 0, $sub2_oid, 0))),
 	qq(f),
 	qq(Subscription stats for subscription '$sub2_name' should be removed.));
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index d427a1c16a..d7385f9bfb 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2118,6 +2118,7 @@ PgStatShared_Function
 PgStatShared_HashEntry
 PgStatShared_IO
 PgStatShared_Relation
+PgStatShared_RelFileNode
 PgStatShared_ReplSlot
 PgStatShared_SLRU
 PgStatShared_Subscription
-- 
2.34.1

