From e3f8642bcbbf76887dadc7b0b12bf7b2e309c547 Mon Sep 17 00:00:00 2001
From: Andrew Johnson <andrewj@metronome.com>
Date: Tue, 10 Jun 2025 09:34:01 -0500
Subject: [PATCH v1] Adding pg_stat_muiltixact view to allow membership usage
 telemetry

---
 doc/src/sgml/monitoring.sgml                  |  63 ++++++++-
 src/backend/access/transam/multixact.c        |   9 ++
 src/backend/catalog/system_views.sql          |   5 +
 src/backend/utils/activity/Makefile           |   1 +
 src/backend/utils/activity/meson.build        |   1 +
 src/backend/utils/activity/pgstat.c           |  18 +++
 src/backend/utils/activity/pgstat_multixact.c | 133 ++++++++++++++++++
 src/backend/utils/adt/pgstatfuncs.c           |  15 ++
 src/include/catalog/pg_proc.dat               |   8 ++
 src/include/pgstat.h                          |  19 +++
 src/include/utils/pgstat_internal.h           |  19 ++-
 src/include/utils/pgstat_kind.h               |   3 +-
 .../pg-stat-multixact-member-count.out        |  50 +++++++
 src/test/isolation/isolation_schedule         |   1 +
 .../specs/pg-stat-multixact-member-count.spec |  92 ++++++++++++
 src/test/regress/expected/rules.out           |   2 +
 16 files changed, 435 insertions(+), 4 deletions(-)
 create mode 100644 src/backend/utils/activity/pgstat_multixact.c
 create mode 100644 src/test/isolation/expected/pg-stat-multixact-member-count.out
 create mode 100644 src/test/isolation/specs/pg-stat-multixact-member-count.spec

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 4265a22d4de..5aebe83c275 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -278,8 +278,9 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
    shared memory statistics) in the views
    <structname>pg_stat_xact_all_tables</structname>,
    <structname>pg_stat_xact_sys_tables</structname>,
-   <structname>pg_stat_xact_user_tables</structname>, and
-   <structname>pg_stat_xact_user_functions</structname>.  These numbers do not act as
+   <structname>pg_stat_xact_user_tables</structname>,
+   <structname>pg_stat_xact_user_functions</structname> and
+   <structname>pg_stat_multixact</structname>.  These numbers do not act as
    stated above; instead they update continuously throughout the transaction.
   </para>
 
@@ -493,6 +494,14 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
      </entry>
      </row>
 
+     <row>
+      <entry><structname>pg_stat_multixact</structname><indexterm><primary>pg_stat_multixact</primary></indexterm></entry>
+      <entry>One row only, showing statistics about multixact membership consumption. See
+       <link linkend="monitoring-pg-stat-multixact-view">
+       <structname>pg_stat_multixact</structname></link> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication_slots</structname><indexterm><primary>pg_stat_replication_slots</primary></indexterm></entry>
       <entry>One row per replication slot, showing statistics about the
@@ -3243,6 +3252,56 @@ description | Waiting for a newly initialized WAL file to reach durable storage
   </para>
  </sect2>
 
+ <sect2 id="monitoring-pg-stat-multixact-view">
+   <title><structname>pg_stat_multixact</structname></title>
+
+   <indexterm>
+     <primary>pg_stat_multixact</primary>
+   </indexterm>
+
+   <para>
+     The <structname>pg_stat_multixact</structname> view will always have
+     a single row, containing data about multixact membership consumption
+     of the cluster.
+   </para>
+
+   <table id="pg-stat-multixact-view" xreflabel="pg_stat_multixact">
+     <title><structname>pg_stat_multixact</structname> View</title>
+   <tgroup cols="1">
+    <thead>
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       Column Type
+      </para>
+      <para>
+       Description
+      </para></entry>
+     </row>
+    </thead>
+
+    <tbody>
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>update_timestamp</structfield> <type>timestamp with time zone</type>
+      </para>
+      <para>
+       Time at which the statistic was last updated.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>members</structfield> <type>bigint</type>
+      </para>
+      <para>
+       Number of multixact members in use.
+      </para></entry>
+     </row>
+    </tbody>
+   </tgroup>
+   </table>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-wal-view">
    <title><structname>pg_stat_wal</structname></title>
 
diff --git a/src/backend/access/transam/multixact.c b/src/backend/access/transam/multixact.c
index 3c06ac45532..524306dfbc9 100644
--- a/src/backend/access/transam/multixact.c
+++ b/src/backend/access/transam/multixact.c
@@ -1262,6 +1262,12 @@ GetNewMultiXactId(int nmembers, MultiXactOffset *offset)
 
 	MultiXactState->nextOffset += nmembers;
 
+	/*
+	 * Record multixact membership space telemetry while we have the lock. We
+	 * do not use the saved variables above because they are stale.
+	 */
+	pgstat_update_multixact_stats(MultiXactState->nextOffset - MultiXactState->oldestOffset);
+
 	LWLockRelease(MultiXactGenLock);
 
 	debug_elog4(DEBUG2, "GetNew: returning %u offset %u", result, *offset);
@@ -2987,6 +2993,9 @@ MultiXactMemberFreezeThreshold(void)
 	if (!ReadMultiXactCounts(&multixacts, &members))
 		return 0;
 
+	/* Record the number of multixact members. */
+	pgstat_update_multixact_stats(members);
+
 	/* If member space utilization is low, no special action is required. */
 	if (members <= MULTIXACT_MEMBER_SAFE_THRESHOLD)
 		return autovacuum_multixact_freeze_max_age;
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 08f780a2e63..1cc1df4aa10 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1201,6 +1201,11 @@ CREATE VIEW pg_stat_wal AS
         w.stats_reset
     FROM pg_stat_get_wal() w;
 
+CREATE VIEW pg_stat_multixact AS
+    SELECT
+        pg_stat_get_multixact_update_timestamp() AS update_timestamp,
+        pg_stat_get_multixact_members() AS members;
+
 CREATE VIEW pg_stat_progress_analyze AS
     SELECT
         S.pid AS pid, S.datid AS datid, D.datname AS datname,
diff --git a/src/backend/utils/activity/Makefile b/src/backend/utils/activity/Makefile
index 9c2443e1ecd..5dba48a0169 100644
--- a/src/backend/utils/activity/Makefile
+++ b/src/backend/utils/activity/Makefile
@@ -26,6 +26,7 @@ OBJS = \
 	pgstat_database.o \
 	pgstat_function.o \
 	pgstat_io.o \
+	pgstat_multixact.o \
 	pgstat_relation.o \
 	pgstat_replslot.o \
 	pgstat_shmem.o \
diff --git a/src/backend/utils/activity/meson.build b/src/backend/utils/activity/meson.build
index d8e56b49c24..e5a47d8ef05 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_multixact.c',
   'pgstat_relation.c',
   'pgstat_replslot.c',
   'pgstat_shmem.c',
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index 8b57845e870..40a80a82f5a 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -479,6 +479,24 @@ static const PgStat_KindInfo pgstat_kind_builtin_infos[PGSTAT_KIND_BUILTIN_SIZE]
 		.reset_all_cb = pgstat_wal_reset_all_cb,
 		.snapshot_cb = pgstat_wal_snapshot_cb,
 	},
+
+	[PGSTAT_KIND_MULTIXACT] = {
+		.name = "multixact",
+
+		.fixed_amount = true,
+		.write_to_file = true,
+
+		.snapshot_ctl_off = offsetof(PgStat_Snapshot, multixact),
+		.shared_ctl_off = offsetof(PgStat_ShmemControl, multixact),
+		.shared_data_off = offsetof(PgStatShared_MultiXact, stats),
+		.shared_data_len = sizeof(((PgStatShared_MultiXact *) 0)->stats),
+
+		.init_shmem_cb = pgstat_multixact_init_shmem_cb,
+		.flush_static_cb = pgstat_flush_multixact_cb,
+		.have_static_pending_cb = pgstat_multixact_have_pending_cb,
+		.reset_all_cb = pgstat_multixact_reset_all_cb,
+		.snapshot_cb = pgstat_multixact_snapshot_cb,
+	},
 };
 
 /*
diff --git a/src/backend/utils/activity/pgstat_multixact.c b/src/backend/utils/activity/pgstat_multixact.c
new file mode 100644
index 00000000000..cc44ea6aad8
--- /dev/null
+++ b/src/backend/utils/activity/pgstat_multixact.c
@@ -0,0 +1,133 @@
+/* -------------------------------------------------------------------------
+ *
+ * pgstat_multixact.c
+ *	  Implementation of multixact statistics.
+ *
+ * This file contains the implementation of multixact statistics. It is kept
+ * separate from pgstat.c to enforce the line between the statistics access /
+ * storage implementation and the details about individual types of
+ * statistics.
+ *
+ * Copyright (c) 2001-2025, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *	  src/backend/utils/activity/pgstat_multixact.c
+ * -------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+#include "utils/pgstat_internal.h"
+#include "utils/timestamp.h"
+
+static bool have_multixact_stats = false;
+
+/*
+ * pgstat_fetch_stat_multixact()
+ *
+ * Support function for the SQL-callable pgstat* functions. Returns
+ * a pointer to the multixact statistics struct.
+ */
+PgStat_MultiXactStats *
+pgstat_fetch_stat_multixact(void)
+{
+	pgstat_snapshot_fixed(PGSTAT_KIND_MULTIXACT);
+
+	return &pgStatLocal.snapshot.multixact;
+}
+
+bool
+pgstat_multixact_have_pending_cb(void)
+{
+	return have_multixact_stats;
+}
+
+void
+pgstat_update_multixact_stats(uint32 nmembers)
+{
+	PgStat_MultiXactStats * local_stats;
+	TimestampTz now;
+
+	now = GetCurrentTimestamp();
+	local_stats = &pgStatLocal.snapshot.multixact;
+	local_stats->num_members_used = nmembers;
+	local_stats->stat_update_timestamp = now;
+	have_multixact_stats = true;
+}
+
+/*
+ * Report multixact statistics. If nowait is true, then this function
+ * will return if the lock is currently being held.
+ *
+ * This function returns true if the lock could not be acquired. Otherwise, false.
+ */
+bool
+pgstat_flush_multixact_cb(bool nowait)
+{
+	PgStatShared_MultiXact	*stats_shmem;
+	LWLock					*shared_lock;
+
+	stats_shmem = &pgStatLocal.shmem->multixact;
+	shared_lock = &pgStatLocal.shmem->multixact.lock;
+
+	// Since this statistic is a gauge, we have to check timestamps
+	// of the shared statistic and the local statistic. If ours is
+	// larger, then we can overwrite the shared statistic.
+	if (!nowait)
+		LWLockAcquire(shared_lock, LW_EXCLUSIVE);
+	else if (!LWLockConditionalAcquire(shared_lock, LW_EXCLUSIVE))
+		return true;
+
+	if (pgStatLocal.snapshot.multixact.stat_update_timestamp <= stats_shmem->stats.stat_update_timestamp)
+	{
+		// Return if our stats are <= the latest update.
+		LWLockRelease(shared_lock);
+		have_multixact_stats = false;
+		return false;
+	}
+
+	// Update multixact member usage and latest timestamp.
+	stats_shmem->stats.num_members_used = pgStatLocal.snapshot.multixact.num_members_used;
+	stats_shmem->stats.stat_update_timestamp = pgStatLocal.snapshot.multixact.stat_update_timestamp;
+	have_multixact_stats = false;
+
+	LWLockRelease(shared_lock);
+	return false;
+}
+
+void
+pgstat_multixact_init_shmem_cb(void *stats)
+{
+	PgStatShared_MultiXact *stats_shmem;
+
+	stats_shmem = (PgStatShared_MultiXact *) stats;
+	LWLockInitialize(&stats_shmem->lock, LWTRANCHE_PGSTATS_DATA);
+}
+
+void
+pgstat_multixact_snapshot_cb(void)
+{
+	PgStat_MultiXactStats *local_snapshot;
+	PgStatShared_MultiXact *stats_shmem;
+
+	local_snapshot = &pgStatLocal.snapshot.multixact;
+	stats_shmem = &pgStatLocal.shmem->multixact;
+
+	LWLockAcquire(&stats_shmem->lock, LW_SHARED);
+	if (local_snapshot->stat_update_timestamp < stats_shmem->stats.stat_update_timestamp)
+	{
+		local_snapshot->stat_update_timestamp = stats_shmem->stats.stat_update_timestamp;
+		local_snapshot->num_members_used = stats_shmem->stats.num_members_used;
+	}
+	LWLockRelease(&stats_shmem->lock);
+}
+
+void
+pgstat_multixact_reset_all_cb(TimestampTz ts)
+{
+	PgStatShared_MultiXact *stats_shmem;
+
+	stats_shmem = &pgStatLocal.shmem->multixact;
+	LWLockAcquire(&stats_shmem->lock, LW_EXCLUSIVE);
+	memset(&stats_shmem->stats, 0, sizeof(stats_shmem->stats));
+	LWLockRelease(&stats_shmem->lock);
+}
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 1c12ddbae49..dc310a588c7 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -1304,6 +1304,18 @@ pg_stat_get_buf_alloc(PG_FUNCTION_ARGS)
 	PG_RETURN_INT64(pgstat_fetch_stat_bgwriter()->buf_alloc);
 }
 
+Datum
+pg_stat_get_multixact_members(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_INT64(pgstat_fetch_stat_multixact()->num_members_used);
+}
+
+Datum
+pg_stat_get_multixact_update_timestamp(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_TIMESTAMPTZ(pgstat_fetch_stat_multixact()->stat_update_timestamp);
+}
+
 /*
 * When adding a new column to the pg_stat_io view and the
 * pg_stat_get_backend_io() function, add a new enum value here above
@@ -1883,6 +1895,7 @@ pg_stat_reset_shared(PG_FUNCTION_ARGS)
 		XLogPrefetchResetStats();
 		pgstat_reset_of_kind(PGSTAT_KIND_SLRU);
 		pgstat_reset_of_kind(PGSTAT_KIND_WAL);
+		pgstat_reset_of_kind(PGSTAT_KIND_MULTIXACT);
 
 		PG_RETURN_VOID();
 	}
@@ -1903,6 +1916,8 @@ pg_stat_reset_shared(PG_FUNCTION_ARGS)
 		pgstat_reset_of_kind(PGSTAT_KIND_SLRU);
 	else if (strcmp(target, "wal") == 0)
 		pgstat_reset_of_kind(PGSTAT_KIND_WAL);
+	else if (strcmp(target, "multixact") == 0)
+		pgstat_reset_of_kind(PGSTAT_KIND_MULTIXACT);
 	else
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index d3d28a263fa..216420a9788 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5991,6 +5991,14 @@
   proname => 'pg_stat_get_buf_alloc', provolatile => 's', proparallel => 'r',
   prorettype => 'int8', proargtypes => '', prosrc => 'pg_stat_get_buf_alloc' },
 
+{ oid => '9999', descr => 'statistics: number of multixact members in use',
+  proname => 'pg_stat_get_multixact_members', provolatile => 's', proparallel => 'r',
+  prorettype => 'int8', proargtypes => '', prosrc => 'pg_stat_get_multixact_members' },
+{ oid => '9998', descr => 'statistics: timestamp of the last time the multixact members count was updated',
+  proname => 'pg_stat_get_multixact_update_timestamp', provolatile => 's',
+  proparallel => 'r', prorettype => 'timestamptz', proargtypes => '',
+  prosrc => 'pg_stat_get_multixact_update_timestamp' },
+
 { oid => '6214', descr => 'statistics: per backend type IO statistics',
   proname => 'pg_stat_get_io', prorows => '30', proretset => 't',
   provolatile => 'v', proparallel => 'r', prorettype => 'record',
diff --git a/src/include/pgstat.h b/src/include/pgstat.h
index 378f2f2c2ba..c635fa9f840 100644
--- a/src/include/pgstat.h
+++ b/src/include/pgstat.h
@@ -265,6 +265,19 @@ typedef struct PgStat_CheckpointerStats
 	TimestampTz stat_reset_timestamp;
 } PgStat_CheckpointerStats;
 
+/* --------
+ * PgStat_MultiXactStats		MultiXact stats
+ *
+ * This struct should contain only actual event counters, because we make use
+ * of pg_memory_is_all_zeros() to detect whether there are any stats updates
+ * to apply.
+ * ---------
+ */
+typedef struct PgStat_MultiXactStats
+{
+	PgStat_Counter num_members_used;
+	TimestampTz stat_update_timestamp;
+} PgStat_MultiXactStats;
 
 /*
  * Types related to counting IO operations
@@ -579,6 +592,12 @@ extern void pgstat_report_checkpointer(void);
 extern PgStat_CheckpointerStats *pgstat_fetch_stat_checkpointer(void);
 
 
+/*
+ * Functions in pgstat_multixact.c
+ */
+extern PgStat_MultiXactStats *pgstat_fetch_stat_multixact(void);
+extern void pgstat_update_multixact_stats(uint32 nmembers);
+
 /*
  * Functions in pgstat_io.c
  */
diff --git a/src/include/utils/pgstat_internal.h b/src/include/utils/pgstat_internal.h
index d5557e6e998..2e0edd6e4c9 100644
--- a/src/include/utils/pgstat_internal.h
+++ b/src/include/utils/pgstat_internal.h
@@ -417,7 +417,12 @@ typedef struct PgStatShared_Wal
 	PgStat_WalStats stats;
 } PgStatShared_Wal;
 
-
+typedef struct PgStatShared_MultiXact
+{
+	/* lock protects ->stats */
+	LWLock		lock;
+	PgStat_MultiXactStats stats;
+} PgStatShared_MultiXact;
 
 /* ----------
  * Types and definitions for different kinds of variable-amount stats.
@@ -501,6 +506,7 @@ typedef struct PgStat_ShmemControl
 	PgStatShared_IO io;
 	PgStatShared_SLRU slru;
 	PgStatShared_Wal wal;
+	PgStatShared_MultiXact multixact;
 
 	/*
 	 * Custom stats data with fixed-numbered objects, indexed by (PgStat_Kind
@@ -535,6 +541,8 @@ typedef struct PgStat_Snapshot
 
 	PgStat_WalStats wal;
 
+	PgStat_MultiXactStats multixact;
+
 	/*
 	 * Data in snapshot for custom fixed-numbered statistics, indexed by
 	 * (PgStat_Kind - PGSTAT_KIND_CUSTOM_MIN).  Each entry is allocated in
@@ -757,6 +765,15 @@ extern void pgstat_wal_reset_all_cb(TimestampTz ts);
 extern void pgstat_wal_snapshot_cb(void);
 
 
+/*
+ * Functions in pgstat_multixact.c
+ */
+extern void pgstat_multixact_init_shmem_cb(void *stats);
+extern bool pgstat_multixact_have_pending_cb(void);
+extern void pgstat_multixact_snapshot_cb(void);
+extern void pgstat_multixact_reset_all_cb(TimestampTz ts);
+extern bool pgstat_flush_multixact_cb(bool nowait);
+
 /*
  * Functions in pgstat_subscription.c
  */
diff --git a/src/include/utils/pgstat_kind.h b/src/include/utils/pgstat_kind.h
index f44169fd5a3..2a99187c104 100644
--- a/src/include/utils/pgstat_kind.h
+++ b/src/include/utils/pgstat_kind.h
@@ -38,9 +38,10 @@
 #define PGSTAT_KIND_IO	10
 #define PGSTAT_KIND_SLRU	11
 #define PGSTAT_KIND_WAL	12
+#define PGSTAT_KIND_MULTIXACT	13
 
 #define PGSTAT_KIND_BUILTIN_MIN PGSTAT_KIND_DATABASE
-#define PGSTAT_KIND_BUILTIN_MAX PGSTAT_KIND_WAL
+#define PGSTAT_KIND_BUILTIN_MAX PGSTAT_KIND_MULTIXACT
 #define PGSTAT_KIND_BUILTIN_SIZE (PGSTAT_KIND_BUILTIN_MAX + 1)
 
 /* Custom stats kinds */
diff --git a/src/test/isolation/expected/pg-stat-multixact-member-count.out b/src/test/isolation/expected/pg-stat-multixact-member-count.out
new file mode 100644
index 00000000000..6151e2dc896
--- /dev/null
+++ b/src/test/isolation/expected/pg-stat-multixact-member-count.out
@@ -0,0 +1,50 @@
+Parsed test spec with 3 sessions
+
+starting permutation: s1_stat s1_lock s2_lock s2_stat s1_commit s2_commit s3_state s3_commit
+step s1_stat: 
+	INSERT INTO pg_stat_multixact_count_state VALUES (1, (SELECT members FROM pg_stat_multixact));
+
+step s1_lock: 
+	SELECT * FROM pg_stat_multixact_count_check FOR SHARE;
+
+value
+-----
+    1
+(1 row)
+
+step s2_lock: 
+	SELECT * FROM pg_stat_multixact_count_check FOR SHARE;
+
+value
+-----
+    1
+(1 row)
+
+step s2_stat: 
+	INSERT INTO pg_stat_multixact_count_state VALUES (2, (SELECT members FROM pg_stat_multixact));
+
+step s1_commit: 
+	COMMIT;
+
+step s2_commit: 
+	COMMIT;
+
+step s3_state: 
+	WITH session_1_count AS (
+		SELECT member_count FROM pg_stat_multixact_count_state WHERE session = 1
+	),
+	session_2_count AS (
+		SELECT member_count FROM pg_stat_multixact_count_state WHERE session = 2
+	)
+	SELECT s2.member_count - s1.member_count AS diff
+	FROM session_1_count s1
+	CROSS JOIN session_2_count s2;
+
+diff
+----
+   2
+(1 row)
+
+step s3_commit: 
+	COMMIT;
+
diff --git a/src/test/isolation/isolation_schedule b/src/test/isolation/isolation_schedule
index e3c669a29c7..8441c320e78 100644
--- a/src/test/isolation/isolation_schedule
+++ b/src/test/isolation/isolation_schedule
@@ -116,3 +116,4 @@ test: serializable-parallel-2
 test: serializable-parallel-3
 test: matview-write-skew
 test: lock-nowait
+test: pg-stat-multixact-member-count
diff --git a/src/test/isolation/specs/pg-stat-multixact-member-count.spec b/src/test/isolation/specs/pg-stat-multixact-member-count.spec
new file mode 100644
index 00000000000..3445a8b5a98
--- /dev/null
+++ b/src/test/isolation/specs/pg-stat-multixact-member-count.spec
@@ -0,0 +1,92 @@
+# Ensure that the view pg_stat_multixact accurately reflects the
+# number of multixact members currently in use.
+setup
+{
+  CREATE TABLE pg_stat_multixact_count_check (
+	value int
+  );
+
+  CREATE TABLE pg_stat_multixact_count_state (
+	session int,
+	member_count int
+  );
+
+  INSERT INTO pg_stat_multixact_count_check VALUES (1);
+}
+
+teardown
+{
+  DROP TABLE pg_stat_multixact_count_check;
+  DROP TABLE pg_stat_multixact_count_state;
+}
+
+session s1
+
+setup
+{
+	BEGIN;
+}
+
+step s1_stat
+{
+	INSERT INTO pg_stat_multixact_count_state VALUES (1, (SELECT members FROM pg_stat_multixact));
+}
+
+step s1_lock
+{
+	SELECT * FROM pg_stat_multixact_count_check FOR SHARE;
+}
+
+step s1_commit
+{
+	COMMIT;
+}
+
+session s2
+
+setup
+{
+	BEGIN;
+}
+
+step s2_lock
+{
+	SELECT * FROM pg_stat_multixact_count_check FOR SHARE;
+}
+
+step s2_stat
+{
+	INSERT INTO pg_stat_multixact_count_state VALUES (2, (SELECT members FROM pg_stat_multixact));
+}
+
+step s2_commit
+{
+	COMMIT;
+}
+
+session s3
+
+setup
+{
+	BEGIN;
+}
+
+step s3_state
+{
+	WITH session_1_count AS (
+		SELECT member_count FROM pg_stat_multixact_count_state WHERE session = 1
+	),
+	session_2_count AS (
+		SELECT member_count FROM pg_stat_multixact_count_state WHERE session = 2
+	)
+	SELECT s2.member_count - s1.member_count AS diff
+	FROM session_1_count s1
+	CROSS JOIN session_2_count s2;
+}
+
+step s3_commit
+{
+	COMMIT;
+}
+
+permutation s1_stat s1_lock s2_lock s2_stat s1_commit s2_commit s3_state s3_commit
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 6cf828ca8d0..0363bc58548 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1934,6 +1934,8 @@ pg_stat_io| SELECT backend_type,
     fsync_time,
     stats_reset
    FROM pg_stat_get_io() b(backend_type, object, context, reads, read_bytes, read_time, writes, write_bytes, write_time, writebacks, writeback_time, extends, extend_bytes, extend_time, hits, evictions, reuses, fsyncs, fsync_time, stats_reset);
+pg_stat_multixact| SELECT pg_stat_get_multixact_update_timestamp() AS update_timestamp,
+    pg_stat_get_multixact_members() AS members;
 pg_stat_progress_analyze| SELECT s.pid,
     s.datid,
     d.datname,
-- 
2.39.5 (Apple Git-154)

