0001-pgstat-add-pgstat_flush_pending-and-pg_stat_flush_pe.patch

application/octet-stream

Filename: 0001-pgstat-add-pgstat_flush_pending-and-pg_stat_flush_pe.patch
Type: application/octet-stream
Part: 1
Message: Improve pg_stat_statements scalability
From 68b7235559bc71aab7a959b6c7ca43a877f3024a Mon Sep 17 00:00:00 2001
From: Sami Imseih <samimseih@gmail.com>
Date: Sun, 10 May 2026 07:06:04 -0500
Subject: [PATCH 1/2] pgstat: add pgstat_flush_pending() and
 pg_stat_flush_pending(pid)

Add infrastructure for flushing pending statistics to shared memory
on demand, both locally and across backends.

pgstat_flush_pending() flushes all pending stats entries in the calling
backend immediately.  Unlike pgstat_report_stat(), it can be called
mid-transaction, making it suitable for view functions that need fresh
shared stats before the transaction ends.

pg_stat_flush_pending(pid) is the SQL-callable interface: if the target
PID is the caller's own backend, it flushes immediately; otherwise it
signals the target backend via PROCSIG_FLUSH_STATS to flush at its next
CHECK_FOR_INTERRUPTS() call.

This addresses the visibility gap where pending stats accumulated in
other backends are not reflected in shared memory views until the next
idle flush or transaction end.
---
 src/backend/storage/ipc/procsignal.c |  3 ++
 src/backend/tcop/postgres.c          |  3 ++
 src/backend/utils/activity/pgstat.c  | 60 ++++++++++++++++++++++++++++
 src/backend/utils/adt/pgstatfuncs.c  | 40 +++++++++++++++++++
 src/backend/utils/init/globals.c     |  1 +
 src/include/catalog/pg_proc.dat      |  6 +++
 src/include/miscadmin.h              |  1 +
 src/include/pgstat.h                 |  3 ++
 src/include/storage/procsignal.h     |  1 +
 9 files changed, 118 insertions(+)

diff --git a/src/backend/storage/ipc/procsignal.c b/src/backend/storage/ipc/procsignal.c
index 264e4c22ca6..6e0f318c42d 100644
--- a/src/backend/storage/ipc/procsignal.c
+++ b/src/backend/storage/ipc/procsignal.c
@@ -711,6 +711,9 @@ procsignal_sigusr1_handler(SIGNAL_ARGS)
 	if (CheckProcSignal(PROCSIG_REPACK_MESSAGE))
 		HandleRepackMessageInterrupt();
 
+	if (CheckProcSignal(PROCSIG_FLUSH_STATS))
+		HandleFlushStatsInterrupt();
+
 	if (CheckProcSignal(PROCSIG_SLOTSYNC_MESSAGE))
 		HandleSlotSyncMessageInterrupt();
 
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index dbef734a93f..4749511235d 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -3609,6 +3609,9 @@ ProcessInterrupts(void)
 	if (LogMemoryContextPending)
 		ProcessLogMemoryContextInterrupt();
 
+	if (FlushStatsPending)
+		ProcessFlushStatsInterrupt();
+
 	if (ParallelApplyMessagePending)
 		ProcessParallelApplyMessages();
 
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index b67da88c7dc..0fbdf7e3bca 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -106,6 +106,7 @@
 
 #include "access/xact.h"
 #include "lib/dshash.h"
+#include "miscadmin.h"
 #include "pgstat.h"
 #include "storage/fd.h"
 #include "storage/ipc.h"
@@ -845,6 +846,65 @@ pgstat_force_next_flush(void)
 	pgStatForceNextFlush = true;
 }
 
+/*
+ * Immediately flush all pending statistics entries to shared memory.
+ *
+ * Unlike pgstat_report_stat(), this can be called mid-transaction.
+ * It is useful for code that needs pending stats visible in shared memory
+ * before the transaction ends (e.g., view functions that read shared stats).
+ */
+void
+pgstat_flush_pending(void)
+{
+	dlist_mutable_iter iter;
+
+	if (dlist_is_empty(&pgStatPending))
+		return;
+
+	dlist_foreach_modify(iter, &pgStatPending)
+	{
+		PgStat_EntryRef *entry_ref =
+			dlist_container(PgStat_EntryRef, pending_node, iter.cur);
+		PgStat_Kind kind = entry_ref->shared_entry->key.kind;
+		const PgStat_KindInfo *kind_info;
+
+		kind_info = pgstat_get_kind_info(kind);
+		if (kind_info->flush_pending_cb &&
+			kind_info->flush_pending_cb(entry_ref, false))
+			pgstat_delete_pending_entry(entry_ref);
+	}
+}
+
+/*
+ * HandleFlushStatsInterrupt
+ *		Handle receipt of a signal indicating that pending stats should be
+ *		flushed to shared memory.
+ *
+ * The actual flush is deferred to ProcessFlushStatsInterrupt(), called from
+ * CHECK_FOR_INTERRUPTS().
+ */
+void
+HandleFlushStatsInterrupt(void)
+{
+	InterruptPending = true;
+	FlushStatsPending = true;
+	/* latch will be set by procsignal_sigusr1_handler */
+}
+
+/*
+ * ProcessFlushStatsInterrupt
+ *		Flush all pending statistics to shared memory.
+ *
+ * Called from CHECK_FOR_INTERRUPTS() when FlushStatsPending is set.
+ */
+void
+ProcessFlushStatsInterrupt(void)
+{
+	FlushStatsPending = false;
+
+	pgstat_flush_pending();
+}
+
 /*
  * Only for use by pgstat_reset_counters()
  */
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 7a9dfa9ba3b..00ddedc2e95 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -28,6 +28,7 @@
 #include "replication/logicallauncher.h"
 #include "storage/proc.h"
 #include "storage/procarray.h"
+#include "storage/procsignal.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/timestamp.h"
@@ -1929,6 +1930,45 @@ pg_stat_force_next_flush(PG_FUNCTION_ARGS)
 	PG_RETURN_VOID();
 }
 
+/*
+ * Signal a backend to flush all its pending statistics to shared memory.
+ * If the target is the current backend, the flush happens immediately.
+ */
+Datum
+pg_stat_flush_pending(PG_FUNCTION_ARGS)
+{
+	int			pid = PG_GETARG_INT32(0);
+	PGPROC	   *proc;
+	ProcNumber	procNumber = INVALID_PROC_NUMBER;
+
+	if (pid == MyProcPid)
+	{
+		pgstat_flush_pending();
+		PG_RETURN_BOOL(true);
+	}
+
+	proc = BackendPidGetProc(pid);
+	if (proc == NULL)
+		proc = AuxiliaryPidGetProc(pid);
+
+	if (proc == NULL)
+	{
+		ereport(WARNING,
+				(errmsg("PID %d is not a PostgreSQL server process", pid)));
+		PG_RETURN_BOOL(false);
+	}
+
+	procNumber = GetNumberFromPGProc(proc);
+	if (SendProcSignal(pid, PROCSIG_FLUSH_STATS, procNumber) < 0)
+	{
+		ereport(WARNING,
+				(errmsg("could not send signal to process %d: %m", pid)));
+		PG_RETURN_BOOL(false);
+	}
+
+	PG_RETURN_BOOL(true);
+}
+
 
 /* Reset all counters for the current database */
 Datum
diff --git a/src/backend/utils/init/globals.c b/src/backend/utils/init/globals.c
index bbd28d14d99..957d4507d04 100644
--- a/src/backend/utils/init/globals.c
+++ b/src/backend/utils/init/globals.c
@@ -39,6 +39,7 @@ volatile sig_atomic_t TransactionTimeoutPending = false;
 volatile sig_atomic_t IdleSessionTimeoutPending = false;
 volatile sig_atomic_t ProcSignalBarrierPending = false;
 volatile sig_atomic_t LogMemoryContextPending = false;
+volatile sig_atomic_t FlushStatsPending = false;
 volatile sig_atomic_t IdleStatsUpdateTimeoutPending = false;
 volatile uint32 InterruptHoldoffCount = 0;
 volatile uint32 QueryCancelHoldoffCount = 0;
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index fa9ae79082b..daedafc6845 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -6209,6 +6209,12 @@
   proname => 'pg_stat_force_next_flush', proisstrict => 'f', provolatile => 'v',
   proparallel => 'r', prorettype => 'void', proargtypes => '',
   prosrc => 'pg_stat_force_next_flush' },
+{ oid => '9953',
+  descr => 'statistics: flush pending stats of the specified backend to shared memory',
+  proname => 'pg_stat_flush_pending', provolatile => 'v',
+  prorettype => 'bool', proargtypes => 'int4',
+  prosrc => 'pg_stat_flush_pending',
+  proacl => '{POSTGRES=X}' },
 { oid => '2274',
   descr => 'statistics: reset collected statistics for current database',
   proname => 'pg_stat_reset', proisstrict => 'f', provolatile => 'v',
diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h
index 8ccdf61246b..2205add6445 100644
--- a/src/include/miscadmin.h
+++ b/src/include/miscadmin.h
@@ -97,6 +97,7 @@ extern PGDLLIMPORT volatile sig_atomic_t TransactionTimeoutPending;
 extern PGDLLIMPORT volatile sig_atomic_t IdleSessionTimeoutPending;
 extern PGDLLIMPORT volatile sig_atomic_t ProcSignalBarrierPending;
 extern PGDLLIMPORT volatile sig_atomic_t LogMemoryContextPending;
+extern PGDLLIMPORT volatile sig_atomic_t FlushStatsPending;
 extern PGDLLIMPORT volatile sig_atomic_t IdleStatsUpdateTimeoutPending;
 
 extern PGDLLIMPORT volatile sig_atomic_t CheckClientConnectionPending;
diff --git a/src/include/pgstat.h b/src/include/pgstat.h
index dfa2e837638..a19473f71d9 100644
--- a/src/include/pgstat.h
+++ b/src/include/pgstat.h
@@ -552,6 +552,9 @@ extern void pgstat_initialize(void);
 /* Functions called from backends */
 extern long pgstat_report_stat(bool force);
 extern void pgstat_force_next_flush(void);
+extern void pgstat_flush_pending(void);
+extern void HandleFlushStatsInterrupt(void);
+extern void ProcessFlushStatsInterrupt(void);
 
 extern void pgstat_reset_counters(void);
 extern void pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid);
diff --git a/src/include/storage/procsignal.h b/src/include/storage/procsignal.h
index aaa158bfd66..5ce66aaeb9a 100644
--- a/src/include/storage/procsignal.h
+++ b/src/include/storage/procsignal.h
@@ -38,6 +38,7 @@ typedef enum
 	PROCSIG_PARALLEL_APPLY_MESSAGE, /* Message from parallel apply workers */
 	PROCSIG_SLOTSYNC_MESSAGE,	/* ask slot synchronization to stop */
 	PROCSIG_REPACK_MESSAGE,		/* Message from repack worker */
+	PROCSIG_FLUSH_STATS,		/* ask backend to flush pending statistics */
 	PROCSIG_RECOVERY_CONFLICT,	/* backend is blocking recovery, check
 								 * PGPROC->pendingRecoveryConflicts for the
 								 * reason */
-- 
2.50.1 (Apple Git-155)