From c64a3a582201fc7d1925d374ad6fa5778b0d4d14 Mon Sep 17 00:00:00 2001
From: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Date: Wed, 28 Jan 2026 07:53:13 +0000
Subject: [PATCH v5 2/4] Add GUC to specify non-transactional statistics flush
 interval

Adding pgstat_flush_interval, a new GUC to set the interval between flushes of
non-transactional statistics.
---
 doc/src/sgml/config.sgml                      | 32 +++++++++++++++++++
 src/backend/access/transam/xlog.c             |  4 +--
 src/backend/utils/activity/pgstat.c           | 16 +++++++++-
 src/backend/utils/activity/pgstat_backend.c   |  4 +--
 src/backend/utils/activity/pgstat_io.c        |  2 +-
 src/backend/utils/activity/pgstat_slru.c      |  2 +-
 src/backend/utils/misc/guc_parameters.dat     | 10 ++++++
 src/backend/utils/misc/postgresql.conf.sample |  1 +
 src/include/pgstat.h                          |  1 +
 src/include/utils/guc_hooks.h                 |  1 +
 10 files changed, 66 insertions(+), 7 deletions(-)
  45.9% doc/src/sgml/
   6.5% src/backend/access/transam/
  31.8% src/backend/utils/activity/
  12.3% src/backend/utils/misc/
   3.2% src/include/

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..3136816a933 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -8834,6 +8834,38 @@ COPY postgres_log FROM '/full/path/to/logfile.csv' WITH csv;
       </listitem>
      </varlistentry>
 
+     <varlistentry id="guc-stats-flush-interval" xreflabel="stats_flush_interval">
+      <term><varname>stats_flush_interval</varname> (<type>integer</type>)
+      <indexterm>
+       <primary><varname>stats_flush_interval</varname> configuration parameter</primary>
+      </indexterm>
+      </term>
+      <listitem>
+       <para>
+        Sets the interval at which non-transactional statistics are made visible
+        during running transactions. Non-transactional statistics include, for
+        example, WAL activity and I/O operations.
+        They become visible at that interval in monitoring views such as
+        <link linkend="monitoring-pg-stat-io-view"> <structname>pg_stat_io</structname></link>
+        and <link linkend="monitoring-pg-stat-wal-view"> <structname>pg_stat_wal</structname></link>
+        during running transactions.
+        If this value is specified without units, it is taken as milliseconds.
+        The default is 10 seconds (<literal>10s</literal>), which is probably
+        about the smallest value you would want in practice for long running
+        transactions.
+       </para>
+       <note>
+        <para>
+         This parameter does not affect transactional statistics such as
+         <structname>pg_stat_all_tables</structname> columns (like
+         <structfield>n_tup_ins</structfield>, <structfield>n_tup_upd</structfield>,
+         <structfield>n_tup_del</structfield>), which are always flushed at transaction
+         boundaries to maintain consistency.
+        </para>
+       </note>
+      </listitem>
+     </varlistentry>
+
      </variablelist>
     </sect2>
 
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 9503aea5b4d..31523dea923 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -1087,7 +1087,7 @@ XLogInsertRecord(XLogRecData *rdata,
 
 		/* Schedule next anytime stats update timeout */
 		if (IsUnderPostmaster && !get_timeout_active(ANYTIME_STATS_UPDATE_TIMEOUT))
-			enable_timeout_after(ANYTIME_STATS_UPDATE_TIMEOUT, PGSTAT_MIN_INTERVAL);
+			enable_timeout_after(ANYTIME_STATS_UPDATE_TIMEOUT, pgstat_flush_interval);
 
 		/* Required for the flush of pending stats WAL data */
 		pgstat_report_fixed = true;
@@ -2073,7 +2073,7 @@ AdvanceXLInsertBuffer(XLogRecPtr upto, TimeLineID tli, bool opportunistic)
 					/* Schedule next anytime stats update timeout */
 					if (IsUnderPostmaster && !get_timeout_active(ANYTIME_STATS_UPDATE_TIMEOUT))
 						enable_timeout_after(ANYTIME_STATS_UPDATE_TIMEOUT,
-											 PGSTAT_MIN_INTERVAL);
+											 pgstat_flush_interval);
 
 					/*
 					 * Required for the flush of pending stats WAL data, per
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index 2c9454677e9..dd174129403 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -203,6 +203,7 @@ static inline bool pgstat_is_kind_valid(PgStat_Kind kind);
 
 bool		pgstat_track_counts = false;
 int			pgstat_fetch_consistency = PGSTAT_FETCH_CONSISTENCY_CACHE;
+int			pgstat_flush_interval = 10000;
 
 
 /* ----------
@@ -1304,7 +1305,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat
 		/* Schedule next anytime stats update timeout */
 		if (kind_info->flush_mode == FLUSH_ANYTIME && IsUnderPostmaster &&
 			!get_timeout_active(ANYTIME_STATS_UPDATE_TIMEOUT))
-			enable_timeout_after(ANYTIME_STATS_UPDATE_TIMEOUT, PGSTAT_MIN_INTERVAL);
+			enable_timeout_after(ANYTIME_STATS_UPDATE_TIMEOUT, pgstat_flush_interval);
 	}
 
 	return entry_ref;
@@ -2172,6 +2173,19 @@ assign_stats_fetch_consistency(int newval, void *extra)
 		force_stats_snapshot_clear = true;
 }
 
+/*
+ * GUC assign_hook for stats_flush_interval.
+ */
+void
+assign_stats_flush_interval(int newval, void *extra)
+{
+	if (get_timeout_active(ANYTIME_STATS_UPDATE_TIMEOUT))
+	{
+		disable_timeout(ANYTIME_STATS_UPDATE_TIMEOUT, false);
+		enable_timeout_after(ANYTIME_STATS_UPDATE_TIMEOUT, newval);
+	}
+}
+
 /*
  * Flushes only FLUSH_ANYTIME stats using non-blocking locks. Transactional
  * stats (FLUSH_AT_TXN_BOUNDARY) remain pending until transaction boundary.
diff --git a/src/backend/utils/activity/pgstat_backend.c b/src/backend/utils/activity/pgstat_backend.c
index 9dcb24db975..f5b8c7b039c 100644
--- a/src/backend/utils/activity/pgstat_backend.c
+++ b/src/backend/utils/activity/pgstat_backend.c
@@ -69,7 +69,7 @@ pgstat_count_backend_io_op_time(IOObject io_object, IOContext io_context,
 
 	/* Schedule next anytime stats update timeout */
 	if (IsUnderPostmaster && !get_timeout_active(ANYTIME_STATS_UPDATE_TIMEOUT))
-		enable_timeout_after(ANYTIME_STATS_UPDATE_TIMEOUT, PGSTAT_MIN_INTERVAL);
+		enable_timeout_after(ANYTIME_STATS_UPDATE_TIMEOUT, pgstat_flush_interval);
 
 	backend_has_iostats = true;
 	pgstat_report_fixed = true;
@@ -89,7 +89,7 @@ pgstat_count_backend_io_op(IOObject io_object, IOContext io_context,
 
 	/* Schedule next anytime stats update timeout */
 	if (IsUnderPostmaster && !get_timeout_active(ANYTIME_STATS_UPDATE_TIMEOUT))
-		enable_timeout_after(ANYTIME_STATS_UPDATE_TIMEOUT, PGSTAT_MIN_INTERVAL);
+		enable_timeout_after(ANYTIME_STATS_UPDATE_TIMEOUT, pgstat_flush_interval);
 
 	backend_has_iostats = true;
 	pgstat_report_fixed = true;
diff --git a/src/backend/utils/activity/pgstat_io.c b/src/backend/utils/activity/pgstat_io.c
index 53dbf2a514b..b69a1e26f7d 100644
--- a/src/backend/utils/activity/pgstat_io.c
+++ b/src/backend/utils/activity/pgstat_io.c
@@ -82,7 +82,7 @@ pgstat_count_io_op(IOObject io_object, IOContext io_context, IOOp io_op,
 
 	/* Schedule next anytime stats update timeout */
 	if (IsUnderPostmaster && !get_timeout_active(ANYTIME_STATS_UPDATE_TIMEOUT))
-		enable_timeout_after(ANYTIME_STATS_UPDATE_TIMEOUT, PGSTAT_MIN_INTERVAL);
+		enable_timeout_after(ANYTIME_STATS_UPDATE_TIMEOUT, pgstat_flush_interval);
 
 	have_iostats = true;
 	pgstat_report_fixed = true;
diff --git a/src/backend/utils/activity/pgstat_slru.c b/src/backend/utils/activity/pgstat_slru.c
index 1d16cde1889..36231ee874b 100644
--- a/src/backend/utils/activity/pgstat_slru.c
+++ b/src/backend/utils/activity/pgstat_slru.c
@@ -226,7 +226,7 @@ get_slru_entry(int slru_idx)
 
 	/* Schedule next anytime stats update timeout */
 	if (IsUnderPostmaster && !get_timeout_active(ANYTIME_STATS_UPDATE_TIMEOUT))
-		enable_timeout_after(ANYTIME_STATS_UPDATE_TIMEOUT, PGSTAT_MIN_INTERVAL);
+		enable_timeout_after(ANYTIME_STATS_UPDATE_TIMEOUT, pgstat_flush_interval);
 
 	have_slrustats = true;
 	pgstat_report_fixed = true;
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3bb43362e51 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2782,6 +2782,16 @@
   assign_hook => 'assign_stats_fetch_consistency',
 },
 
+{ name => 'stats_flush_interval', type => 'int', context => 'PGC_USERSET', group => 'STATS_CUMULATIVE',
+  short_desc => 'Sets the interval between flushes of non-transactional statistics.',
+  flags => 'GUC_UNIT_MS',
+  variable => 'pgstat_flush_interval',
+  boot_val => '10000',
+  min => '1000',
+  max => 'INT_MAX',
+  assign_hook => 'assign_stats_flush_interval'
+},
+
 { name => 'subtransaction_buffers', type => 'int', context => 'PGC_POSTMASTER', group => 'RESOURCES_MEM',
   short_desc => 'Sets the size of the dedicated buffer pool used for the subtransaction cache.',
   long_desc => '0 means use a fraction of "shared_buffers".',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..6ce5a250170 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -669,6 +669,7 @@
 #track_wal_io_timing = off
 #track_functions = none                 # none, pl, all
 #stats_fetch_consistency = cache        # cache, none, snapshot
+#stats_flush_interval = 10s             # in milliseconds
 
 
 # - Monitoring -
diff --git a/src/include/pgstat.h b/src/include/pgstat.h
index 1651f16f966..e0f222695bf 100644
--- a/src/include/pgstat.h
+++ b/src/include/pgstat.h
@@ -816,6 +816,7 @@ extern PgStat_WalStats *pgstat_fetch_stat_wal(void);
 extern PGDLLIMPORT bool pgstat_track_counts;
 extern PGDLLIMPORT int pgstat_track_functions;
 extern PGDLLIMPORT int pgstat_fetch_consistency;
+extern PGDLLIMPORT int pgstat_flush_interval;
 
 
 /*
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..3a2ae6c41cd 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -132,6 +132,7 @@ extern bool check_session_authorization(char **newval, void **extra, GucSource s
 extern void assign_session_authorization(const char *newval, void *extra);
 extern void assign_session_replication_role(int newval, void *extra);
 extern void assign_stats_fetch_consistency(int newval, void *extra);
+extern void assign_stats_flush_interval(int newval, void *extra);
 extern bool check_ssl(bool *newval, void **extra, GucSource source);
 extern bool check_stage_log_stats(bool *newval, void **extra, GucSource source);
 extern bool check_standard_conforming_strings(bool *newval, void **extra,
-- 
2.34.1

