v9b-0003-Lower-pg_stat_io_histogram-shared-memory-use-by-.patch

text/x-patch

Filename: v9b-0003-Lower-pg_stat_io_histogram-shared-memory-use-by-.patch
Type: text/x-patch
Part: 6
Message: Re: pg_stat_io_histogram

Patch

Format: format-patch
Series: patch 0003
Subject: Lower pg_stat_io_histogram shared memory use by using array with indirect offsets.
File+
src/backend/Makefile 1 1
src/backend/utils/activity/Makefile 1 0
src/backend/utils/activity/meson.build 1 0
src/backend/utils/activity/pgstat_io.c 52 253
src/backend/utils/activity/pgstat_io_genslots.c 275 0
src/backend/utils/adt/pgstatfuncs.c 11 4
src/backend/utils/Makefile 13 2
src/common/meson.build 1 1
src/include/Makefile 2 2
src/include/pgstat.h 18 1
src/include/utils/meson.build 29 0
From 85a48297be1511e29d79b6800c9b4d46b102b708 Mon Sep 17 00:00:00 2001
From: Jakub Wartak <jakub.wartak@enterprisedb.com>
Date: Wed, 18 Mar 2026 20:57:54 +0100
Subject: [PATCH v9b 3/3] Lower pg_stat_io_histogram shared memory use by using
 array with indirect offsets.

We use pgstat_track_io_*() family of functions to derive the length of static
array that is allocated in shared memory region during startup. As the number
of valid combinations of backend types vs I/O object/context/operations is
coming from semi-runtime pgstat_io_get_sum_tracked() function, it cannot be
preprocessed, so we need to come up with #define PGSTAT_IO_HIST_BUCKET_SLOTS
somehow. In order to do that - and avoid that C limitations - we transfer
some routines to pgstat_io_genslots.c, which has added additional standalone
special mode (int main()), that can be used to generate pgstat_io_genslots
binary and used to generate dynamic header file with the proper size.
---
 src/backend/Makefile                          |   2 +-
 src/backend/utils/Makefile                    |  15 +-
 src/backend/utils/activity/Makefile           |   1 +
 src/backend/utils/activity/meson.build        |   1 +
 src/backend/utils/activity/pgstat_io.c        | 305 +++---------------
 .../utils/activity/pgstat_io_genslots.c       | 275 ++++++++++++++++
 src/backend/utils/adt/pgstatfuncs.c           |  15 +-
 src/common/meson.build                        |   2 +-
 src/include/Makefile                          |   4 +-
 src/include/pgstat.h                          |  19 +-
 src/include/utils/meson.build                 |  29 ++
 11 files changed, 404 insertions(+), 264 deletions(-)
 create mode 100644 src/backend/utils/activity/pgstat_io_genslots.c

diff --git a/src/backend/Makefile b/src/backend/Makefile
index ba53cd9d998..c80df4bbde0 100644
--- a/src/backend/Makefile
+++ b/src/backend/Makefile
@@ -165,7 +165,7 @@ submake-nodes-headers:
 	$(MAKE) -C nodes generated-header-symlinks
 
 # run this unconditionally to avoid needing to know its dependencies here:
-submake-utils-headers:
+submake-utils-headers: submake-nodes-headers
 	$(MAKE) -C utils generated-header-symlinks
 
 .PHONY: submake-catalog-headers submake-nodes-headers submake-utils-headers
diff --git a/src/backend/utils/Makefile b/src/backend/utils/Makefile
index 81b4a956bda..54d04b248bb 100644
--- a/src/backend/utils/Makefile
+++ b/src/backend/utils/Makefile
@@ -43,7 +43,7 @@ generated-header-symlinks: $(top_builddir)/src/include/utils/header-stamp submak
 submake-adt-headers:
 	$(MAKE) -C adt jsonpath_gram.h
 
-$(SUBDIRS:%=%-recursive): fmgr-stamp errcodes.h guc_tables.inc.c pgstat_wait_event.c wait_event_funcs_data.c wait_event_types.h
+$(SUBDIRS:%=%-recursive): fmgr-stamp errcodes.h guc_tables.inc.c pgstat_wait_event.c wait_event_funcs_data.c wait_event_types.h pgstat_io_genslots.h
 
 # fmgr-stamp records the last time we ran Gen_fmgrtab.pl.  We don't rely on
 # the timestamps of the individual output files, because the Perl script
@@ -64,6 +64,16 @@ wait_event_funcs_data.c: wait_event_types.h
 wait_event_types.h: $(top_srcdir)/src/backend/utils/activity/wait_event_names.txt $(top_srcdir)/src/backend/utils/activity/generate-wait_event_types.pl
 	$(PERL) $(top_srcdir)/src/backend/utils/activity/generate-wait_event_types.pl --code $<
 
+pgstat_io_genslots.c: pgstat_io_genslots.h errcodes.h
+
+pgstat_io_genslots.h: pgstat_io_genslots
+	$(top_srcdir)/src/backend/utils/pgstat_io_genslots > $(top_srcdir)/src/include/utils/pgstat_io_genslots.h
+
+pgstat_io_genslots: $(top_srcdir)/src/backend/utils/activity/pgstat_io_genslots.c
+	# for some reason src/backend/utils/errcodes.h is not accessible without
+	# this
+	$(CC) $(CFLAGS) $(CPPFLAGS) -I../../../src/backend -DPGSTAT_IO_GENSLOTS_STANDALONE $< -o pgstat_io_genslots
+
 ifeq ($(enable_dtrace), yes)
 probes.h: postprocess_dtrace.sed probes.h.tmp
 	sed -f $^ >$@
@@ -79,7 +89,7 @@ endif
 # These generated headers must be symlinked into src/include/.
 # We use header-stamp to record that we've done this because the symlinks
 # themselves may appear older than fmgr-stamp.
-$(top_builddir)/src/include/utils/header-stamp: fmgr-stamp errcodes.h probes.h guc_tables.inc.c pgstat_wait_event.c wait_event_funcs_data.c wait_event_types.h
+$(top_builddir)/src/include/utils/header-stamp: fmgr-stamp errcodes.h pgstat_io_genslots.h probes.h guc_tables.inc.c pgstat_wait_event.c wait_event_funcs_data.c wait_event_types.h
 	cd '$(dir $@)' && for file in fmgroids.h fmgrprotos.h errcodes.h probes.h guc_tables.inc.c pgstat_wait_event.c wait_event_funcs_data.c wait_event_types.h; do \
 	  rm -f $$file && $(LN_S) "../../../$(subdir)/$$file" . ; \
 	done
@@ -100,3 +110,4 @@ clean:
 	rm -f probes.h probes.h.tmp
 	rm -f fmgroids.h fmgrprotos.h fmgrtab.c fmgr-stamp errcodes.h guc_tables.inc.c
 	rm -f wait_event_types.h pgstat_wait_event.c wait_event_funcs_data.c
+	rm -f pgstat_io_genslots pgstat_io_genslots.h
diff --git a/src/backend/utils/activity/Makefile b/src/backend/utils/activity/Makefile
index c37bfb350bb..b6fda50f651 100644
--- a/src/backend/utils/activity/Makefile
+++ b/src/backend/utils/activity/Makefile
@@ -16,6 +16,7 @@ include $(top_builddir)/src/Makefile.global
 override CPPFLAGS := -I. -I$(srcdir) $(CPPFLAGS)
 
 OBJS = \
+	pgstat_io_genslots.o \
 	backend_progress.o \
 	backend_status.o \
 	pgstat.o \
diff --git a/src/backend/utils/activity/meson.build b/src/backend/utils/activity/meson.build
index 53bd5a246ca..87522ef846e 100644
--- a/src/backend/utils/activity/meson.build
+++ b/src/backend/utils/activity/meson.build
@@ -11,6 +11,7 @@ backend_sources += files(
   'pgstat_database.c',
   'pgstat_function.c',
   'pgstat_io.c',
+  'pgstat_io_genslots.c',
   'pgstat_relation.c',
   'pgstat_replslot.c',
   'pgstat_shmem.c',
diff --git a/src/backend/utils/activity/pgstat_io.c b/src/backend/utils/activity/pgstat_io.c
index f571d558154..23e0e250bff 100644
--- a/src/backend/utils/activity/pgstat_io.c
+++ b/src/backend/utils/activity/pgstat_io.c
@@ -67,26 +67,6 @@ pgstat_bktype_io_stats_valid(PgStat_BktypeIO *backend_io,
 	return true;
 }
 
-int
-pgstat_bktype_count_potentially_used(BackendType bktype)
-{
-	int cnt = 0;
-	for (int io_object = 0; io_object < IOOBJECT_NUM_TYPES; io_object++)
-	{
-		for (int io_context = 0; io_context < IOCONTEXT_NUM_TYPES; io_context++)
-		{
-			for (int io_op = 0; io_op < IOOP_NUM_TYPES; io_op++)
-			{
-				/* we do track it */
-				if (pgstat_tracks_io_op(bktype, io_object, io_context, io_op))
-					cnt++;
-			}
-		}
-	}
-
-	return cnt;
-}
-
 void
 pgstat_count_io_op(IOObject io_object, IOContext io_context, IOOp io_op,
 				   uint32 cnt, uint64 bytes)
@@ -250,6 +230,7 @@ pgstat_io_flush_cb(bool nowait)
 {
 	LWLock	   *bktype_lock;
 	PgStat_BktypeIO *bktype_shstats;
+	PgStat_IO	*bk_io;
 
 	if (!have_iostats)
 		return false;
@@ -257,6 +238,7 @@ pgstat_io_flush_cb(bool nowait)
 	bktype_lock = &pgStatLocal.shmem->io.locks[MyBackendType];
 	bktype_shstats =
 		&pgStatLocal.shmem->io.stats.stats[MyBackendType];
+	bk_io = &pgStatLocal.shmem->io.stats;
 
 	if (!nowait)
 		LWLockAcquire(bktype_lock, LW_EXCLUSIVE);
@@ -287,14 +269,22 @@ pgstat_io_flush_cb(bool nowait)
 				 * by using indirect offsets from the pending_hist_time_buckets dynamic array
 				 * (accessed with offsets to save memory) into shared memory.
 				 */
-				if(PendingIOStats.pending_hist_time_buckets != NULL)
+				if(PendingIOStats.pending_hist_time_buckets != NULL) {
+					int16 bktype_shstats_off = bktype_shstats->hist_time_buckets_offsets[io_object][io_context][io_op];
+					/*
+					 * -1 means here that such mapping doesn't have slot
+					 *  (based on pgstat_track_io_*())
+					 */
+					if(bktype_shstats_off == -1)
+						continue;
+
 					for(int b = 0; b < PGSTAT_IO_HIST_BUCKETS; b++) {
 						int pending_off = PendingIOStats.pending_hist_time_buckets_offsets[io_object][io_context][io_op];
-						if(pending_off != -1) {
-							bktype_shstats->hist_time_buckets[io_object][io_context][io_op][b] +=
+						if(pending_off != -1)
+							bk_io->hist_time_buckets_slots[bktype_shstats_off][b] +=
 								PendingIOStats.pending_hist_time_buckets[pending_off][b];
-						}
 					}
+				}
 			}
 		}
 	}
@@ -409,12 +399,12 @@ pgstat_io_init_backend_cb(void)
 	}
 	else
 		PendingIOStats.pending_hist_time_buckets = NULL;
-
 }
 
 void
 pgstat_io_init_shmem_cb(void *stats)
 {
+	uint16 histogram_slots = 0;
 	PgStatShared_IO *stat_shmem = (PgStatShared_IO *) stats;
 
 	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
@@ -422,6 +412,29 @@ pgstat_io_init_shmem_cb(void *stats)
 
 	/* this might end up being lazily allocated in pgstat_io_snapshot_cb() */
 	pgStatLocal.snapshot.io = NULL;
+
+	/*
+	 * Establish indirect mapping from PgStat_BktypeIO.hist_time_buckets_offsets[][][] to
+	 * PgStatIO.hist_time_buckets_slots[x]
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++) {
+		for (int io_object = 0; io_object < IOOBJECT_NUM_TYPES; io_object++)
+		{
+			for (int io_context = 0; io_context < IOCONTEXT_NUM_TYPES; io_context++)
+			{
+				for (int io_op = 0; io_op < IOOP_NUM_TYPES; io_op++)
+				{
+					if (pgstat_tracks_io_op(i, io_object, io_context, io_op)) {
+						stat_shmem->stats.stats[i].hist_time_buckets_offsets[io_object][io_context][io_op] =
+							histogram_slots++;
+					} else
+						stat_shmem->stats.stats[i].hist_time_buckets_offsets[io_object][io_context][io_op] =
+							-1;
+				}
+			}
+		}
+	}
+
 }
 
 void
@@ -436,12 +449,17 @@ pgstat_io_reset_all_cb(TimestampTz ts)
 
 		/*
 		 * Use the lock in the first BackendType's PgStat_BktypeIO to protect
-		 * the reset timestamp as well.
+		 * the reset timestamp and the memory used for I/O latency data.
 		 */
-		if (i == 0)
+		if (i == 0) {
 			pgStatLocal.shmem->io.stats.stat_reset_timestamp = ts;
 
-		memset(bktype_shstats, 0, sizeof(*bktype_shstats));
+			memset(pgStatLocal.shmem->io.stats.hist_time_buckets_slots, 0,
+			  sizeof(pgStatLocal.shmem->io.stats.hist_time_buckets_slots));
+		}
+
+		/* Avoid resetting our indirect mapping offsets */
+		memset(bktype_shstats, 0, offsetof(PgStat_BktypeIO, hist_time_buckets_offsets));
 		LWLockRelease(bktype_lock);
 	}
 }
@@ -465,237 +483,18 @@ pgstat_io_snapshot_cb(void)
 		 * Use the lock in the first BackendType's PgStat_BktypeIO to protect
 		 * the reset timestamp as well.
 		 */
-		if (i == 0)
+		if (i == 0) {
 			pgStatLocal.snapshot.io->stat_reset_timestamp =
 				pgStatLocal.shmem->io.stats.stat_reset_timestamp;
 
+			memcpy(pgStatLocal.snapshot.io->hist_time_buckets_slots,
+				pgStatLocal.shmem->io.stats.hist_time_buckets_slots, sizeof(pgStatLocal.snapshot.io->hist_time_buckets_slots));
+		}
+
 		/* using struct assignment due to better type safety */
 		*bktype_snap = *bktype_shstats;
-		LWLockRelease(bktype_lock);
-	}
-}
 
-/*
-* IO statistics are not collected for all BackendTypes.
-*
-* The following BackendTypes do not participate in the cumulative stats
-* subsystem or do not perform IO on which we currently track:
-* - Dead-end backend because it is not connected to shared memory and
-*   doesn't do any IO
-* - Syslogger because it is not connected to shared memory
-* - Archiver because most relevant archiving IO is delegated to a
-*   specialized command or module
-*
-* Function returns true if BackendType participates in the cumulative stats
-* subsystem for IO and false if it does not.
-*
-* When adding a new BackendType, also consider adding relevant restrictions to
-* pgstat_tracks_io_object() and pgstat_tracks_io_op().
-*/
-bool
-pgstat_tracks_io_bktype(BackendType bktype)
-{
-	/*
-	 * List every type so that new backend types trigger a warning about
-	 * needing to adjust this switch.
-	 */
-	switch (bktype)
-	{
-		case B_INVALID:
-		case B_DEAD_END_BACKEND:
-		case B_ARCHIVER:
-		case B_LOGGER:
-			return false;
-
-		case B_AUTOVAC_LAUNCHER:
-		case B_AUTOVAC_WORKER:
-		case B_BACKEND:
-		case B_BG_WORKER:
-		case B_BG_WRITER:
-		case B_CHECKPOINTER:
-		case B_IO_WORKER:
-		case B_SLOTSYNC_WORKER:
-		case B_STANDALONE_BACKEND:
-		case B_STARTUP:
-		case B_WAL_RECEIVER:
-		case B_WAL_SENDER:
-		case B_WAL_SUMMARIZER:
-		case B_WAL_WRITER:
-			return true;
+		LWLockRelease(bktype_lock);
 	}
-
-	return false;
 }
 
-/*
- * Some BackendTypes do not perform IO on certain IOObjects or in certain
- * IOContexts. Some IOObjects are never operated on in some IOContexts. Check
- * that the given BackendType is expected to do IO in the given IOContext and
- * on the given IOObject and that the given IOObject is expected to be operated
- * on in the given IOContext.
- */
-bool
-pgstat_tracks_io_object(BackendType bktype, IOObject io_object,
-						IOContext io_context)
-{
-	bool		no_temp_rel;
-
-	/*
-	 * Some BackendTypes should never track IO statistics.
-	 */
-	if (!pgstat_tracks_io_bktype(bktype))
-		return false;
-
-	/*
-	 * Currently, IO on IOOBJECT_WAL objects can only occur in the
-	 * IOCONTEXT_NORMAL and IOCONTEXT_INIT IOContexts.
-	 */
-	if (io_object == IOOBJECT_WAL &&
-		(io_context != IOCONTEXT_NORMAL &&
-		 io_context != IOCONTEXT_INIT))
-		return false;
-
-	/*
-	 * Currently, IO on temporary relations can only occur in the
-	 * IOCONTEXT_NORMAL IOContext.
-	 */
-	if (io_context != IOCONTEXT_NORMAL &&
-		io_object == IOOBJECT_TEMP_RELATION)
-		return false;
-
-	/*
-	 * In core Postgres, only regular backends and WAL Sender processes
-	 * executing queries will use local buffers and operate on temporary
-	 * relations. Parallel workers will not use local buffers (see
-	 * InitLocalBuffers()); however, extensions leveraging background workers
-	 * have no such limitation, so track IO on IOOBJECT_TEMP_RELATION for
-	 * BackendType B_BG_WORKER.
-	 */
-	no_temp_rel = bktype == B_AUTOVAC_LAUNCHER || bktype == B_BG_WRITER ||
-		bktype == B_CHECKPOINTER || bktype == B_AUTOVAC_WORKER ||
-		bktype == B_STANDALONE_BACKEND || bktype == B_STARTUP ||
-		bktype == B_WAL_SUMMARIZER || bktype == B_WAL_WRITER ||
-		bktype == B_WAL_RECEIVER;
-
-	if (no_temp_rel && io_context == IOCONTEXT_NORMAL &&
-		io_object == IOOBJECT_TEMP_RELATION)
-		return false;
-
-	/*
-	 * Some BackendTypes only perform IO under IOOBJECT_WAL, hence exclude all
-	 * rows for all the other objects for these.
-	 */
-	if ((bktype == B_WAL_SUMMARIZER || bktype == B_WAL_RECEIVER ||
-		 bktype == B_WAL_WRITER) && io_object != IOOBJECT_WAL)
-		return false;
-
-	/*
-	 * Some BackendTypes do not currently perform any IO in certain
-	 * IOContexts, and, while it may not be inherently incorrect for them to
-	 * do so, excluding those rows from the view makes the view easier to use.
-	 */
-	if ((bktype == B_CHECKPOINTER || bktype == B_BG_WRITER) &&
-		(io_context == IOCONTEXT_BULKREAD ||
-		 io_context == IOCONTEXT_BULKWRITE ||
-		 io_context == IOCONTEXT_VACUUM))
-		return false;
-
-	if (bktype == B_AUTOVAC_LAUNCHER && io_context == IOCONTEXT_VACUUM)
-		return false;
-
-	if ((bktype == B_AUTOVAC_WORKER || bktype == B_AUTOVAC_LAUNCHER) &&
-		io_context == IOCONTEXT_BULKWRITE)
-		return false;
-
-	return true;
-}
-
-/*
- * Some BackendTypes will never do certain IOOps and some IOOps should not
- * occur in certain IOContexts or on certain IOObjects. Check that the given
- * IOOp is valid for the given BackendType in the given IOContext and on the
- * given IOObject. Note that there are currently no cases of an IOOp being
- * invalid for a particular BackendType only within a certain IOContext and/or
- * only on a certain IOObject.
- */
-bool
-pgstat_tracks_io_op(BackendType bktype, IOObject io_object,
-					IOContext io_context, IOOp io_op)
-{
-	bool		strategy_io_context;
-
-	/* if (io_context, io_object) will never collect stats, we're done */
-	if (!pgstat_tracks_io_object(bktype, io_object, io_context))
-		return false;
-
-	/*
-	 * Some BackendTypes will not do certain IOOps.
-	 */
-	if (bktype == B_BG_WRITER &&
-		(io_op == IOOP_READ || io_op == IOOP_EVICT || io_op == IOOP_HIT))
-		return false;
-
-	if (bktype == B_CHECKPOINTER &&
-		((io_object != IOOBJECT_WAL && io_op == IOOP_READ) ||
-		 (io_op == IOOP_EVICT || io_op == IOOP_HIT)))
-		return false;
-
-	if ((bktype == B_AUTOVAC_LAUNCHER || bktype == B_BG_WRITER ||
-		 bktype == B_CHECKPOINTER) && io_op == IOOP_EXTEND)
-		return false;
-
-	/*
-	 * Some BackendTypes do not perform reads with IOOBJECT_WAL.
-	 */
-	if (io_object == IOOBJECT_WAL && io_op == IOOP_READ &&
-		(bktype == B_WAL_RECEIVER || bktype == B_BG_WRITER ||
-		 bktype == B_AUTOVAC_LAUNCHER || bktype == B_AUTOVAC_WORKER ||
-		 bktype == B_WAL_WRITER))
-		return false;
-
-	/*
-	 * Temporary tables are not logged and thus do not require fsync'ing.
-	 * Writeback is not requested for temporary tables.
-	 */
-	if (io_object == IOOBJECT_TEMP_RELATION &&
-		(io_op == IOOP_FSYNC || io_op == IOOP_WRITEBACK))
-		return false;
-
-	/*
-	 * Some IOOps are not valid in certain IOContexts and some IOOps are only
-	 * valid in certain contexts.
-	 */
-	if (io_context == IOCONTEXT_BULKREAD && io_op == IOOP_EXTEND)
-		return false;
-
-	strategy_io_context = io_context == IOCONTEXT_BULKREAD ||
-		io_context == IOCONTEXT_BULKWRITE || io_context == IOCONTEXT_VACUUM;
-
-	/*
-	 * IOOP_REUSE is only relevant when a BufferAccessStrategy is in use.
-	 */
-	if (!strategy_io_context && io_op == IOOP_REUSE)
-		return false;
-
-	/*
-	 * IOOBJECT_WAL IOObject will not do certain IOOps depending on IOContext.
-	 */
-	if (io_object == IOOBJECT_WAL && io_context == IOCONTEXT_INIT &&
-		!(io_op == IOOP_WRITE || io_op == IOOP_FSYNC))
-		return false;
-
-	if (io_object == IOOBJECT_WAL && io_context == IOCONTEXT_NORMAL &&
-		!(io_op == IOOP_WRITE || io_op == IOOP_READ || io_op == IOOP_FSYNC))
-		return false;
-
-	/*
-	 * IOOP_FSYNC IOOps done by a backend using a BufferAccessStrategy are
-	 * counted in the IOCONTEXT_NORMAL IOContext. See comment in
-	 * register_dirty_segment() for more details.
-	 */
-	if (strategy_io_context && io_op == IOOP_FSYNC)
-		return false;
-
-
-	return true;
-}
diff --git a/src/backend/utils/activity/pgstat_io_genslots.c b/src/backend/utils/activity/pgstat_io_genslots.c
new file mode 100644
index 00000000000..34c857ce697
--- /dev/null
+++ b/src/backend/utils/activity/pgstat_io_genslots.c
@@ -0,0 +1,275 @@
+#include "postgres.h"
+#include "pgstat.h"
+
+/*
+ * This can be build as standalone to output how may PGSTAT_IO_HIST_BUCKET_SLOTS
+ * we need for allocation within shared memory.
+ */
+#ifdef PGSTAT_IO_GENSLOTS_STANDALONE
+#include <stdio.h>
+/* restore orginal printf, so that it works without pg_printf() */
+#undef printf
+
+int main(void) {
+	int calculated_size = pgstat_io_get_sum_tracked();
+	printf("%s\n" , "#pragma once");
+	printf("#define PGSTAT_IO_HIST_BUCKET_SLOTS %d+1\n", calculated_size);
+	return 0;
+}
+#endif
+
+int
+pgstat_bktype_count_potentially_used(BackendType bktype)
+{
+	int cnt = 0;
+	for (int io_object = 0; io_object < IOOBJECT_NUM_TYPES; io_object++)
+	{
+		for (int io_context = 0; io_context < IOCONTEXT_NUM_TYPES; io_context++)
+		{
+			for (int io_op = 0; io_op < IOOP_NUM_TYPES; io_op++)
+			{
+				/* we do track it */
+				if (pgstat_tracks_io_op(bktype, io_object, io_context, io_op))
+					cnt++;
+			}
+		}
+	}
+
+	return cnt;
+}
+
+int
+pgstat_io_get_sum_tracked(void)
+{
+	int sum = 0;
+
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		sum += pgstat_bktype_count_potentially_used(i);
+
+	return sum;
+}
+
+/*
+* IO statistics are not collected for all BackendTypes.
+*
+* The following BackendTypes do not participate in the cumulative stats
+* subsystem or do not perform IO on which we currently track:
+* - Dead-end backend because it is not connected to shared memory and
+*   doesn't do any IO
+* - Syslogger because it is not connected to shared memory
+* - Archiver because most relevant archiving IO is delegated to a
+*   specialized command or module
+*
+* Function returns true if BackendType participates in the cumulative stats
+* subsystem for IO and false if it does not.
+*
+* When adding a new BackendType, also consider adding relevant restrictions to
+* pgstat_tracks_io_object() and pgstat_tracks_io_op().
+*/
+bool
+pgstat_tracks_io_bktype(BackendType bktype)
+{
+	/*
+	 * List every type so that new backend types trigger a warning about
+	 * needing to adjust this switch.
+	 */
+	switch (bktype)
+	{
+		case B_INVALID:
+		case B_DEAD_END_BACKEND:
+		case B_ARCHIVER:
+		case B_LOGGER:
+			return false;
+
+		case B_AUTOVAC_LAUNCHER:
+		case B_AUTOVAC_WORKER:
+		case B_BACKEND:
+		case B_BG_WORKER:
+		case B_BG_WRITER:
+		case B_CHECKPOINTER:
+		case B_IO_WORKER:
+		case B_SLOTSYNC_WORKER:
+		case B_STANDALONE_BACKEND:
+		case B_STARTUP:
+		case B_WAL_RECEIVER:
+		case B_WAL_SENDER:
+		case B_WAL_SUMMARIZER:
+		case B_WAL_WRITER:
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Some BackendTypes do not perform IO on certain IOObjects or in certain
+ * IOContexts. Some IOObjects are never operated on in some IOContexts. Check
+ * that the given BackendType is expected to do IO in the given IOContext and
+ * on the given IOObject and that the given IOObject is expected to be operated
+ * on in the given IOContext.
+ */
+bool
+pgstat_tracks_io_object(BackendType bktype, IOObject io_object,
+						IOContext io_context)
+{
+	bool		no_temp_rel;
+
+	/*
+	 * Some BackendTypes should never track IO statistics.
+	 */
+	if (!pgstat_tracks_io_bktype(bktype))
+		return false;
+
+	/*
+	 * Currently, IO on IOOBJECT_WAL objects can only occur in the
+	 * IOCONTEXT_NORMAL and IOCONTEXT_INIT IOContexts.
+	 */
+	if (io_object == IOOBJECT_WAL &&
+		(io_context != IOCONTEXT_NORMAL &&
+		 io_context != IOCONTEXT_INIT))
+		return false;
+
+	/*
+	 * Currently, IO on temporary relations can only occur in the
+	 * IOCONTEXT_NORMAL IOContext.
+	 */
+	if (io_context != IOCONTEXT_NORMAL &&
+		io_object == IOOBJECT_TEMP_RELATION)
+		return false;
+
+	/*
+	 * In core Postgres, only regular backends and WAL Sender processes
+	 * executing queries will use local buffers and operate on temporary
+	 * relations. Parallel workers will not use local buffers (see
+	 * InitLocalBuffers()); however, extensions leveraging background workers
+	 * have no such limitation, so track IO on IOOBJECT_TEMP_RELATION for
+	 * BackendType B_BG_WORKER.
+	 */
+	no_temp_rel = bktype == B_AUTOVAC_LAUNCHER || bktype == B_BG_WRITER ||
+		bktype == B_CHECKPOINTER || bktype == B_AUTOVAC_WORKER ||
+		bktype == B_STANDALONE_BACKEND || bktype == B_STARTUP ||
+		bktype == B_WAL_SUMMARIZER || bktype == B_WAL_WRITER ||
+		bktype == B_WAL_RECEIVER;
+
+	if (no_temp_rel && io_context == IOCONTEXT_NORMAL &&
+		io_object == IOOBJECT_TEMP_RELATION)
+		return false;
+
+	/*
+	 * Some BackendTypes only perform IO under IOOBJECT_WAL, hence exclude all
+	 * rows for all the other objects for these.
+	 */
+	if ((bktype == B_WAL_SUMMARIZER || bktype == B_WAL_RECEIVER ||
+		 bktype == B_WAL_WRITER) && io_object != IOOBJECT_WAL)
+		return false;
+
+	/*
+	 * Some BackendTypes do not currently perform any IO in certain
+	 * IOContexts, and, while it may not be inherently incorrect for them to
+	 * do so, excluding those rows from the view makes the view easier to use.
+	 */
+	if ((bktype == B_CHECKPOINTER || bktype == B_BG_WRITER) &&
+		(io_context == IOCONTEXT_BULKREAD ||
+		 io_context == IOCONTEXT_BULKWRITE ||
+		 io_context == IOCONTEXT_VACUUM))
+		return false;
+
+	if (bktype == B_AUTOVAC_LAUNCHER && io_context == IOCONTEXT_VACUUM)
+		return false;
+
+	if ((bktype == B_AUTOVAC_WORKER || bktype == B_AUTOVAC_LAUNCHER) &&
+		io_context == IOCONTEXT_BULKWRITE)
+		return false;
+
+	return true;
+}
+
+/*
+ * Some BackendTypes will never do certain IOOps and some IOOps should not
+ * occur in certain IOContexts or on certain IOObjects. Check that the given
+ * IOOp is valid for the given BackendType in the given IOContext and on the
+ * given IOObject. Note that there are currently no cases of an IOOp being
+ * invalid for a particular BackendType only within a certain IOContext and/or
+ * only on a certain IOObject.
+ */
+bool
+pgstat_tracks_io_op(BackendType bktype, IOObject io_object,
+					IOContext io_context, IOOp io_op)
+{
+	bool		strategy_io_context;
+
+	/* if (io_context, io_object) will never collect stats, we're done */
+	if (!pgstat_tracks_io_object(bktype, io_object, io_context))
+		return false;
+
+	/*
+	 * Some BackendTypes will not do certain IOOps.
+	 */
+	if (bktype == B_BG_WRITER &&
+		(io_op == IOOP_READ || io_op == IOOP_EVICT || io_op == IOOP_HIT))
+		return false;
+
+	if (bktype == B_CHECKPOINTER &&
+		((io_object != IOOBJECT_WAL && io_op == IOOP_READ) ||
+		 (io_op == IOOP_EVICT || io_op == IOOP_HIT)))
+		return false;
+
+	if ((bktype == B_AUTOVAC_LAUNCHER || bktype == B_BG_WRITER ||
+		 bktype == B_CHECKPOINTER) && io_op == IOOP_EXTEND)
+		return false;
+
+	/*
+	 * Some BackendTypes do not perform reads with IOOBJECT_WAL.
+	 */
+	if (io_object == IOOBJECT_WAL && io_op == IOOP_READ &&
+		(bktype == B_WAL_RECEIVER || bktype == B_BG_WRITER ||
+		 bktype == B_AUTOVAC_LAUNCHER || bktype == B_AUTOVAC_WORKER ||
+		 bktype == B_WAL_WRITER))
+		return false;
+
+	/*
+	 * Temporary tables are not logged and thus do not require fsync'ing.
+	 * Writeback is not requested for temporary tables.
+	 */
+	if (io_object == IOOBJECT_TEMP_RELATION &&
+		(io_op == IOOP_FSYNC || io_op == IOOP_WRITEBACK))
+		return false;
+
+	/*
+	 * Some IOOps are not valid in certain IOContexts and some IOOps are only
+	 * valid in certain contexts.
+	 */
+	if (io_context == IOCONTEXT_BULKREAD && io_op == IOOP_EXTEND)
+		return false;
+
+	strategy_io_context = io_context == IOCONTEXT_BULKREAD ||
+		io_context == IOCONTEXT_BULKWRITE || io_context == IOCONTEXT_VACUUM;
+
+	/*
+	 * IOOP_REUSE is only relevant when a BufferAccessStrategy is in use.
+	 */
+	if (!strategy_io_context && io_op == IOOP_REUSE)
+		return false;
+
+	/*
+	 * IOOBJECT_WAL IOObject will not do certain IOOps depending on IOContext.
+	 */
+	if (io_object == IOOBJECT_WAL && io_context == IOCONTEXT_INIT &&
+		!(io_op == IOOP_WRITE || io_op == IOOP_FSYNC))
+		return false;
+
+	if (io_object == IOOBJECT_WAL && io_context == IOCONTEXT_NORMAL &&
+		!(io_op == IOOP_WRITE || io_op == IOOP_READ || io_op == IOOP_FSYNC))
+		return false;
+
+	/*
+	 * IOOP_FSYNC IOOps done by a backend using a BufferAccessStrategy are
+	 * counted in the IOCONTEXT_NORMAL IOContext. See comment in
+	 * register_dirty_segment() for more details.
+	 */
+	if (strategy_io_context && io_op == IOOP_FSYNC)
+		return false;
+
+
+	return true;
+}
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index b457d771474..017c2b8676b 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -1671,6 +1671,7 @@ typedef enum hist_io_stat_col
  */
 static void
 pg_stat_io_histogram_build_tuples(ReturnSetInfo *rsinfo,
+						PgStat_IO  *backends_io_stats,
 						PgStat_BktypeIO *bktype_stats,
 						BackendType bktype,
 						TimestampTz stat_reset_timestamp)
@@ -1705,6 +1706,8 @@ pg_stat_io_histogram_build_tuples(ReturnSetInfo *rsinfo,
 					bool		nulls[HIST_IO_NUM_COLUMNS] = {0};
 					RangeBound	lower, upper;
 					RangeType	*range;
+					int16 bktype_hist_time_bucket_off;
+					uint64 bktype_bucket;
 
 					values[HIST_IO_COL_BACKEND_TYPE] = bktype_desc;
 					values[HIST_IO_COL_OBJECT] = CStringGetTextDatum(obj_name);
@@ -1732,9 +1735,13 @@ pg_stat_io_histogram_build_tuples(ReturnSetInfo *rsinfo,
 					range = make_range(typcache, &lower, &upper, false, NULL);
 					values[HIST_IO_COL_BUCKET_US] = RangeTypePGetDatum(range);
 
-					/* bucket count */
-					values[HIST_IO_COL_COUNT] = Int64GetDatum(
-						bktype_stats->hist_time_buckets[io_obj][io_context][io_op][bucket]);
+					/* get bucket count, access indirectly */
+					bktype_hist_time_bucket_off = bktype_stats->hist_time_buckets_offsets[io_obj][io_context][io_op];
+					if(bktype_hist_time_bucket_off == -1)
+						continue;
+					Assert(bktype_hist_time_bucket_off < PGSTAT_IO_HIST_BUCKET_SLOTS);
+					bktype_bucket = backends_io_stats->hist_time_buckets_slots[bktype_hist_time_bucket_off][bucket];
+					values[HIST_IO_COL_COUNT] = Int64GetDatum(bktype_bucket);
 
 					if (stat_reset_timestamp != 0)
 						values[HIST_IO_COL_RESET_TIME] = TimestampTzGetDatum(stat_reset_timestamp);
@@ -1780,7 +1787,7 @@ pg_stat_get_io_histogram(PG_FUNCTION_ARGS)
 			continue;
 
 		/* save tuples with data from this PgStat_BktypeIO */
-		pg_stat_io_histogram_build_tuples(rsinfo, bktype_stats, bktype,
+		pg_stat_io_histogram_build_tuples(rsinfo, backends_io_stats, bktype_stats, bktype,
 								backends_io_stats->stat_reset_timestamp);
 	}
 
diff --git a/src/common/meson.build b/src/common/meson.build
index 4f9b8b8263d..02eba7d3b27 100644
--- a/src/common/meson.build
+++ b/src/common/meson.build
@@ -136,7 +136,7 @@ common_sources_frontend_static = [
 pgcommon = {}
 pgcommon_variants = {
   '_srv': internal_lib_args + {
-    'sources': common_sources + [lwlocknames_h] + [wait_event_types_h],
+    'sources': common_sources + [lwlocknames_h] + [wait_event_types_h] + [pgstat_io_genslots_header],
     'dependencies': [backend_common_code],
   },
   '': default_lib_args + {
diff --git a/src/include/Makefile b/src/include/Makefile
index ac673f4cf17..49d94048a74 100644
--- a/src/include/Makefile
+++ b/src/include/Makefile
@@ -84,7 +84,7 @@ install: all installdirs
 	  $(INSTALL_DATA) $(srcdir)/$$dir/*.h '$(DESTDIR)$(includedir_server)'/$$dir || exit; \
 	done
 ifeq ($(vpath_build),yes)
-	for file in storage/lwlocknames.h utils/probes.h utils/wait_event_types.h; do \
+	for file in storage/lwlocknames.h utils/probes.h utils/wait_event_types.h utils/pgstat_io_genslots.h; do \
 	  $(INSTALL_DATA) $$file '$(DESTDIR)$(includedir_server)'/$$file || exit; \
 	done
 endif
@@ -106,7 +106,7 @@ uninstall:
 clean:
 	rm -f utils/fmgroids.h utils/fmgrprotos.h utils/guc_tables.inc.c utils/errcodes.h utils/header-stamp
 	rm -f utils/pgstat_wait_event.c utils/wait_event_funcs_data.c
-	rm -f storage/lwlocknames.h utils/probes.h utils/wait_event_types.h
+	rm -f storage/lwlocknames.h utils/probes.h utils/wait_event_types.h utils/pgstat_io_genslots.h
 	rm -f nodes/nodetags.h nodes/header-stamp
 	$(MAKE) -C catalog clean
 
diff --git a/src/include/pgstat.h b/src/include/pgstat.h
index 15e5743ca98..a6e8f7a5b85 100644
--- a/src/include/pgstat.h
+++ b/src/include/pgstat.h
@@ -19,6 +19,17 @@
 #include "utils/backend_status.h"	/* for backward compatibility */	/* IWYU pragma: export */
 #include "utils/pgstat_kind.h"
 
+#ifdef PGSTAT_IO_GENSLOTS_STANDALONE
+/*
+ * This is workaround to avoid error that we do not have
+ * PGSTAT_IO_HIST_BUCKET_SLOTS later on when included by pgstat_io_genslots.c
+ * in standalone mode (launched by meson/autoconf) as it includes pgstat.h
+ */
+#define PGSTAT_IO_HIST_BUCKET_SLOTS 0
+#else
+/* include dynamically generated PGSTAT_IO_HIST_BUCKET_SLOTS */
+#include "utils/pgstat_io_genslots.h"
+#endif
 
 /* avoid including access/transam.h */
 typedef struct FullTransactionId FullTransactionId;
@@ -341,7 +352,9 @@ typedef struct PgStat_BktypeIO
 	uint64		bytes[IOOBJECT_NUM_TYPES][IOCONTEXT_NUM_TYPES][IOOP_NUM_TYPES];
 	PgStat_Counter counts[IOOBJECT_NUM_TYPES][IOCONTEXT_NUM_TYPES][IOOP_NUM_TYPES];
 	PgStat_Counter times[IOOBJECT_NUM_TYPES][IOCONTEXT_NUM_TYPES][IOOP_NUM_TYPES];
-	uint64		hist_time_buckets[IOOBJECT_NUM_TYPES][IOCONTEXT_NUM_TYPES][IOOP_NUM_TYPES][PGSTAT_IO_HIST_BUCKETS];
+
+	/* indirect offset to PgStat_IO (parent structure).hist_time_buckets_offsets */
+	int16		hist_time_buckets_offsets[IOOBJECT_NUM_TYPES][IOCONTEXT_NUM_TYPES][IOOP_NUM_TYPES];
 } PgStat_BktypeIO;
 
 typedef struct PgStat_PendingIO
@@ -372,6 +385,9 @@ typedef struct PgStat_IO
 {
 	TimestampTz stat_reset_timestamp;
 	PgStat_BktypeIO stats[BACKEND_NUM_TYPES];
+
+	/* PGSTAT_IO_HIST_BUCKET_SLOTS comes from dynamically generated pgstat_io_genslots.h */
+	uint64 hist_time_buckets_slots[PGSTAT_IO_HIST_BUCKET_SLOTS][PGSTAT_IO_HIST_BUCKETS];
 } PgStat_IO;
 
 typedef struct PgStat_StatDBEntry
@@ -637,6 +653,7 @@ extern PgStat_CheckpointerStats *pgstat_fetch_stat_checkpointer(void);
 extern bool pgstat_bktype_io_stats_valid(PgStat_BktypeIO *backend_io,
 										 BackendType bktype);
 extern int pgstat_bktype_count_potentially_used(BackendType bktype);
+extern int pgstat_io_get_sum_tracked(void);
 extern void pgstat_count_io_op(IOObject io_object, IOContext io_context,
 							   IOOp io_op, uint32 cnt, uint64 bytes);
 extern instr_time pgstat_prepare_io_time(bool track_io_guc);
diff --git a/src/include/utils/meson.build b/src/include/utils/meson.build
index fd3a2352df5..d22e4891b6b 100644
--- a/src/include/utils/meson.build
+++ b/src/include/utils/meson.build
@@ -77,6 +77,35 @@ fmgrtab_target = custom_target('fmgrtab',
 generated_backend_headers += fmgrtab_target[0]
 generated_backend_headers += fmgrtab_target[1]
 
+# Build standalone executable that is capable of calculating number of
+# really needed pgstat_io histogram slots (this is done to minimize used
+# shared memory).
+pgstat_io_genslots = executable('pgstat_io_genslots',
+  # As this code uses several headers on it's own, we need to be sure
+  # that generation of other headers (other custom_targets) have completed
+  # generation otherwise we may get errors that they don't exist.
+  '../../backend/utils/activity/pgstat_io_genslots.c', generated_nodes[0], errcodes,
+  c_args: '-DPGSTAT_IO_GENSLOTS_STANDALONE',
+  include_directories: [postgres_inc],
+  kwargs: default_bin_args + {
+    'install': false,
+  },
+)
+bin_targets += pgstat_io_genslots
+
+# Now use above executable to print #define PGSTAT_IO_HIST_BUCKET_SLOTS
+# and save it to pgstat_io_genslots.h that is going to be included when
+# server is going to be built.
+pgstat_io_genslots_header = custom_target('pgstat_io_genslots_header',
+  output: 'pgstat_io_genslots.h',
+  command: [pgstat_io_genslots],
+  capture: true,
+  build_by_default: true,
+  install: true,
+  install_dir: dir_include_server / 'utils',
+)
+generated_backend_headers += pgstat_io_genslots_header
+
 # autoconf generates the file there, ensure we get a conflict
 generated_sources_ac += {
   'src/backend/utils': fmgrtab_output + ['errcodes.h', 'wait_event_types.h', 'probes.h', 'fmgr-stamp'],
-- 
2.43.0