From 41858380d82dbf9fe627cfc84d8827dc27424720 Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Tue, 10 Jun 2025 09:34:01 -0500 Subject: [PATCH] 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 fa78031ccbbf..b334686f84d4 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 pg_stat_xact_all_tables, pg_stat_xact_sys_tables, - pg_stat_xact_user_tables, and - pg_stat_xact_user_functions. These numbers do not act as + pg_stat_xact_user_tables, + pg_stat_xact_user_functions and + pg_stat_multixact. These numbers do not act as stated above; instead they update continuously throughout the transaction. @@ -493,6 +494,14 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser + + pg_stat_multixactpg_stat_multixact + One row only, showing statistics about multixact membership consumption. See + + pg_stat_multixact for details. + + + pg_stat_replication_slotspg_stat_replication_slots One row per replication slot, showing statistics about the @@ -3254,6 +3263,56 @@ description | Waiting for a newly initialized WAL file to reach durable storage + + <structname>pg_stat_multixact</structname> + + + pg_stat_multixact + + + + The pg_stat_multixact view will always have + a single row, containing data about multixact membership consumption + of the cluster. + + + + <structname>pg_stat_multixact</structname> View + + + + + Column Type + + + Description + + + + + + + + update_timestamp timestamp with time zone + + + Time at which the statistic was last updated. + + + + + + members bigint + + + Number of multixact members in use. + + + + +
+
+ <structname>pg_stat_wal</structname> diff --git a/src/backend/access/transam/multixact.c b/src/backend/access/transam/multixact.c index 3cb09c3d5987..cc53b774699f 100644 --- a/src/backend/access/transam/multixact.c +++ b/src/backend/access/transam/multixact.c @@ -1259,6 +1259,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); @@ -2927,6 +2933,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 77c693f630e4..5bba933e53cb 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -1209,6 +1209,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 9c2443e1ecd3..5dba48a01693 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 d8e56b49c247..e5a47d8ef053 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 6bc91ce0dadd..33843ea7e2c1 100644 --- a/src/backend/utils/activity/pgstat.c +++ b/src/backend/utils/activity/pgstat.c @@ -480,6 +480,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 000000000000..19fb3dcb1e0c --- /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 c756c2bebaaa..95cec31534d4 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 118d6da1ace0..02aab1786856 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5992,6 +5992,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 202bd2d5aced..6145439e3a22 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 6cf00008f633..314dce9dd005 100644 --- a/src/include/utils/pgstat_internal.h +++ b/src/include/utils/pgstat_internal.h @@ -410,7 +410,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. @@ -494,6 +499,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 @@ -528,6 +534,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 @@ -746,6 +754,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 eb5f0b3ae6db..f409b95482ae 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 000000000000..6151e2dc896c --- /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 e3c669a29c7a..8441c320e782 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 000000000000..3445a8b5a984 --- /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 6509fda77a99..45000ce4117e 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1938,6 +1938,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,