From: Bertrand Drouvot Date: Wed, 29 Jul 2026 14:13:28 +0000 Subject: [PATCH v1 3/5] pgstat: move WAL statistics to new per-backend infrastructure PGSTAT_KIND_BACKEND stores each backend's WAL, Lock, and IO statistics together in one variable-numbered entry keyed by ProcNumber. Move WAL statistics into a dedicated ProcNumber keyed dshash associated with the fixed WAL statistics kind. The global WAL stats hold data for backends that have exited, while the dshash holds statistics for live backends. Build pg_stat_wal snapshots by copying the global stats and adding every live per-backend entry. Transfer the current process's entry into the global stats before deleting it at process exit or ProcNumber reuse. Transfer all entries before a clean shutdown writes the statistics file. Remove WAL counters from PGSTAT_KIND_BACKEND and make use of the new infrastructure in pg_stat_get_backend_wal(). This also makes WAL statistics available for auxiliary and shared memory-only workers. Update the documentation to describe the new behavior. Author: Bertrand Drouvot Reviewed-by: Discussion: --- doc/src/sgml/monitoring.sgml | 7 +- src/backend/utils/activity/pgstat.c | 18 ++ src/backend/utils/activity/pgstat_backend.c | 75 -------- src/backend/utils/activity/pgstat_wal.c | 179 +++++++++++++++++--- src/backend/utils/adt/pgstatfuncs.c | 37 +++- src/include/pgstat.h | 3 +- src/include/utils/pgstat_internal.h | 22 ++- src/tools/pgindent/typedefs.list | 1 + 8 files changed, 232 insertions(+), 110 deletions(-) 3.6% doc/src/sgml/ 78.0% src/backend/utils/activity/ 10.2% src/backend/utils/adt/ 6.0% src/include/utils/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 099e9b6f4e9..bd5881beb61 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -5853,10 +5853,6 @@ description | Waiting for a newly initialized WAL file to reach durable storage Returns WAL statistics about the backend with the specified process ID. The output fields are exactly the same as the ones in the pg_stat_wal view. - - - The function does not return WAL statistics for the checkpointer, - the background writer, the startup process and the autovacuum launcher. @@ -5996,7 +5992,8 @@ description | Waiting for a newly initialized WAL file to reach durable storage wal: Reset all the counters shown in the - pg_stat_wal view. + pg_stat_wal view, as well as per-backend + WAL statistics returned by pg_stat_get_backend_wal. diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c index efc7d427f22..19a98cdd8b7 100644 --- a/src/backend/utils/activity/pgstat.c +++ b/src/backend/utils/activity/pgstat.c @@ -535,6 +535,11 @@ static const PgStat_KindInfo pgstat_kind_builtin_infos[PGSTAT_KIND_BUILTIN_SIZE] .init_shmem_cb = pgstat_wal_init_shmem_cb, .reset_all_cb = pgstat_wal_reset_all_cb, .snapshot_cb = pgstat_wal_snapshot_cb, + + .per_backend_data_off = offsetof(PgStatShared_WalBackendEntry, stats), + .per_backend_data_len = sizeof(PgStat_WalStats), + .per_backend_hash_handle_off = offsetof(PgStatShared_Wal, backend_hash_handle), + .per_backend_acc_cb = pgstat_wal_per_backend_acc_cb, }, }; @@ -646,6 +651,9 @@ pgstat_before_server_shutdown(int code, Datum arg) */ if (code == 0) { + /* Transfer all live per-backend stats before writing the stats file. */ + pgstat_wal_acc_all_backends(); + pgStatLocal.shmem->is_shutdown = true; pgstat_write_statsfile(); } @@ -689,6 +697,9 @@ pgstat_shutdown_hook(int code, Datum arg) if (!pgstat_drop_entry(PGSTAT_KIND_BACKEND, InvalidOid, MyProcNumber, false)) pgstat_request_entry_refs_gc(); + /* Accumulate per-backend WAL stats into the global stats */ + pgstat_wal_acc_backend_cb(); + pgstat_detach_shmem(); #ifdef USE_ASSERT_CHECKING @@ -709,6 +720,13 @@ pgstat_initialize(void) pgstat_attach_shmem(); + /* + * NB: need to accept that there might be stats from an older backend that + * used the same proc number. Accumulate them into the global stats before + * we start using the entry. + */ + pgstat_wal_acc_backend_cb(); + /* * Create and cache per-backend statistics entries here. This also covers * processes that never call InitPostgres(), such as shared-memory-only diff --git a/src/backend/utils/activity/pgstat_backend.c b/src/backend/utils/activity/pgstat_backend.c index b736b2ccc6f..76b970a8c41 100644 --- a/src/backend/utils/activity/pgstat_backend.c +++ b/src/backend/utils/activity/pgstat_backend.c @@ -41,14 +41,6 @@ static PgStat_BackendPending PendingBackendStats; static bool backend_has_iostats = false; static bool backend_has_lockstats = false; -/* - * WAL usage counters saved from pgWalUsage at the previous call to - * pgstat_flush_backend(). This is used to calculate how much WAL usage - * happens between pgstat_flush_backend() calls, by subtracting the - * previous counters from the current ones. - */ -static WalUsage prevBackendWalUsage; - /* * Utility routines to report I/O stats for backends, kept here to avoid * exposing PendingBackendStats to the outside world. @@ -244,58 +236,6 @@ pgstat_flush_backend_entry_io(PgStat_EntryRef *entry_ref) backend_has_iostats = false; } -/* - * To determine whether WAL usage happened. - */ -static inline bool -pgstat_backend_wal_have_pending(void) -{ - return (pgWalUsage.wal_records != prevBackendWalUsage.wal_records); -} - -/* - * Flush out locally pending backend WAL statistics. Locking is managed - * by the caller. - */ -static void -pgstat_flush_backend_entry_wal(PgStat_EntryRef *entry_ref) -{ - PgStatShared_Backend *shbackendent; - PgStat_WalCounters *bktype_shstats; - WalUsage wal_usage_diff = {0}; - - /* - * This function can be called even if nothing at all has happened for WAL - * statistics. In this case, avoid unnecessarily modifying the stats - * entry. - */ - if (!pgstat_backend_wal_have_pending()) - return; - - shbackendent = (PgStatShared_Backend *) entry_ref->shared_stats; - bktype_shstats = &shbackendent->stats.wal_counters; - - /* - * Calculate how much WAL usage counters were increased by subtracting the - * previous counters from the current ones. - */ - WalUsageAccumDiff(&wal_usage_diff, &pgWalUsage, &prevBackendWalUsage); - -#define WALSTAT_ACC(fld, var_to_add) \ - (bktype_shstats->fld += var_to_add.fld) - WALSTAT_ACC(wal_buffers_full, wal_usage_diff); - WALSTAT_ACC(wal_records, wal_usage_diff); - WALSTAT_ACC(wal_fpi, wal_usage_diff); - WALSTAT_ACC(wal_bytes, wal_usage_diff); - WALSTAT_ACC(wal_fpi_bytes, wal_usage_diff); -#undef WALSTAT_ACC - - /* - * Save the current counters for the subsequent calculation of WAL usage. - */ - prevBackendWalUsage = pgWalUsage; -} - /* * Flush out locally pending backend lock statistics. Locking is managed * by the caller. @@ -345,11 +285,6 @@ pgstat_flush_backend(bool nowait, uint32 flags) if ((flags & PGSTAT_BACKEND_FLUSH_IO) && backend_has_iostats) has_pending_data = true; - /* Some WAL data pending? */ - if ((flags & PGSTAT_BACKEND_FLUSH_WAL) && - pgstat_backend_wal_have_pending()) - has_pending_data = true; - /* Some lock data pending? */ if ((flags & PGSTAT_BACKEND_FLUSH_LOCK) && backend_has_lockstats) has_pending_data = true; @@ -366,9 +301,6 @@ pgstat_flush_backend(bool nowait, uint32 flags) if (flags & PGSTAT_BACKEND_FLUSH_IO) pgstat_flush_backend_entry_io(entry_ref); - if (flags & PGSTAT_BACKEND_FLUSH_WAL) - pgstat_flush_backend_entry_wal(entry_ref); - if (flags & PGSTAT_BACKEND_FLUSH_LOCK) pgstat_flush_backend_entry_lock(entry_ref); @@ -411,13 +343,6 @@ pgstat_create_backend(ProcNumber procnum) MemSet(&PendingBackendStats, 0, sizeof(PgStat_BackendPending)); backend_has_iostats = false; backend_has_lockstats = false; - - /* - * Initialize prevBackendWalUsage with pgWalUsage so that - * pgstat_backend_flush_cb() can calculate how much pgWalUsage counters - * are increased by subtracting prevBackendWalUsage from pgWalUsage. - */ - prevBackendWalUsage = pgWalUsage; } /* diff --git a/src/backend/utils/activity/pgstat_wal.c b/src/backend/utils/activity/pgstat_wal.c index 183e0a7a97b..ece4d91ed70 100644 --- a/src/backend/utils/activity/pgstat_wal.c +++ b/src/backend/utils/activity/pgstat_wal.c @@ -8,6 +8,11 @@ * storage implementation and the details about individual types of * statistics. * + * WAL statistics use a per-backend dshash to avoid double-counting. Each + * backend flushes WAL usage to its own entry in the dshash (keyed by + * ProcNumber). The global pg_stat_wal view aggregates the global stats + * (which holds stats from exited backends) plus all live per-backend entries. + * * Copyright (c) 2001-2026, PostgreSQL Global Development Group * * IDENTIFICATION @@ -52,7 +57,6 @@ pgstat_report_wal(bool force) /* flush wal stats */ (void) pgstat_wal_flush_cb(nowait); - pgstat_flush_backend(nowait, PGSTAT_BACKEND_FLUSH_WAL); /* flush IO stats */ pgstat_flush_io(nowait); @@ -84,13 +88,15 @@ pgstat_wal_have_pending(void) * Calculate how much WAL usage counters have increased by subtracting the * previous counters from the current ones. * + * Flush WAL usage counters to the per-backend dshash entry. + * * If nowait is true, this function returns true if the lock could not be * acquired. Otherwise return false. */ bool pgstat_wal_flush_cb(bool nowait) { - PgStatShared_Wal *stats_shmem = &pgStatLocal.shmem->wal; + PgStatShared_WalBackendEntry *entry; WalUsage wal_usage_diff = {0}; Assert(IsUnderPostmaster || !IsPostmasterEnvironment); @@ -105,19 +111,18 @@ pgstat_wal_flush_cb(bool nowait) return false; /* - * We don't update the WAL usage portion of the local WalStats elsewhere. * Calculate how much WAL usage counters were increased by subtracting the * previous counters from the current ones. */ WalUsageAccumDiff(&wal_usage_diff, &pgWalUsage, &prevWalUsage); - if (!nowait) - LWLockAcquire(&stats_shmem->lock, LW_EXCLUSIVE); - else if (!LWLockConditionalAcquire(&stats_shmem->lock, LW_EXCLUSIVE)) - return true; + entry = pgstat_lock_my_per_backend_entry(PGSTAT_KIND_WAL, nowait); + + if (entry == NULL) + return nowait; #define WALSTAT_ACC(fld, var_to_add) \ - (stats_shmem->stats.wal_counters.fld += var_to_add.fld) + (entry->stats.wal_counters.fld += var_to_add.fld) WALSTAT_ACC(wal_records, wal_usage_diff); WALSTAT_ACC(wal_fpi, wal_usage_diff); WALSTAT_ACC(wal_bytes, wal_usage_diff); @@ -125,7 +130,7 @@ pgstat_wal_flush_cb(bool nowait) WALSTAT_ACC(wal_buffers_full, wal_usage_diff); #undef WALSTAT_ACC - LWLockRelease(&stats_shmem->lock); + LWLockRelease(&entry->header.lock); /* * Save the current counters for the subsequent calculation of WAL usage. @@ -157,21 +162,157 @@ pgstat_wal_init_shmem_cb(void *stats) void pgstat_wal_reset_all_cb(TimestampTz ts) { - PgStatShared_Wal *stats_shmem = &pgStatLocal.shmem->wal; + PgStatShared_Wal *shmem = &pgStatLocal.shmem->wal; + dshash_seq_status hstat; + PgStatShared_WalBackendEntry *entry; + dshash_table *hash; + + hash = pgstat_per_backend_attach(PGSTAT_KIND_WAL); + + /* + * Hold the kind lock while resetting both the global stats and live + * entries. Transfers hold the same lock, so pre-reset counters cannot be + * moved into the global stats after it is reset. + */ + LWLockAcquire(&shmem->lock, LW_EXCLUSIVE); + memset(&shmem->stats, 0, sizeof(shmem->stats)); + shmem->stats.stat_reset_timestamp = ts; + + /* Reset all per-backend entries */ + if (hash != NULL) + { + dshash_seq_init(&hstat, hash, true); + while ((entry = dshash_seq_next(&hstat)) != NULL) + { + LWLockAcquire(&entry->header.lock, LW_EXCLUSIVE); + memset(&entry->stats.wal_counters, 0, sizeof(PgStat_WalCounters)); + entry->stats.stat_reset_timestamp = ts; + LWLockRelease(&entry->header.lock); + } + dshash_seq_term(&hstat); + } - LWLockAcquire(&stats_shmem->lock, LW_EXCLUSIVE); - memset(&stats_shmem->stats, 0, sizeof(stats_shmem->stats)); - stats_shmem->stats.stat_reset_timestamp = ts; - LWLockRelease(&stats_shmem->lock); + LWLockRelease(&shmem->lock); } +/* + * Build WAL stats snapshot by aggregating global stats and all live + * per-backend entries. + */ void pgstat_wal_snapshot_cb(void) { - PgStatShared_Wal *stats_shmem = &pgStatLocal.shmem->wal; + PgStatShared_Wal *shmem = &pgStatLocal.shmem->wal; + PgStat_WalStats *snap = &pgStatLocal.snapshot.wal; + dshash_table *hash; + + hash = pgstat_per_backend_attach(PGSTAT_KIND_WAL); + + /* + * Prevent entries from moving to the global stats between copying it and + * scanning the per-backend hash. + */ + LWLockAcquire(&shmem->lock, LW_SHARED); + memcpy(snap, &shmem->stats, sizeof(PgStat_WalStats)); + + /* Add in all live per-backend entries */ + if (hash != NULL) + pgstat_per_backend_snapshot(PGSTAT_KIND_WAL, hash, snap); - LWLockAcquire(&stats_shmem->lock, LW_SHARED); - memcpy(&pgStatLocal.snapshot.wal, &stats_shmem->stats, - sizeof(pgStatLocal.snapshot.wal)); - LWLockRelease(&stats_shmem->lock); + LWLockRelease(&shmem->lock); +} + +/* Macro to accumulate WAL counters from src into dst */ +#define WAL_ACCUMULATE_COUNTERS(dst, src) \ +do { \ + (dst).wal_records += (src).wal_records; \ + (dst).wal_fpi += (src).wal_fpi; \ + (dst).wal_bytes += (src).wal_bytes; \ + (dst).wal_fpi_bytes += (src).wal_fpi_bytes; \ + (dst).wal_buffers_full += (src).wal_buffers_full; \ +} while (0) + +/* + * Accumulate one per-backend WAL entry into a snapshot or the global stats. + */ +void +pgstat_wal_per_backend_acc_cb(void *dst, void *entry) +{ + PgStat_WalStats *stats = dst; + PgStatShared_WalBackendEntry *e = (PgStatShared_WalBackendEntry *) entry; + + WAL_ACCUMULATE_COUNTERS(stats->wal_counters, e->stats.wal_counters); +} + +/* + * Accumulate a backend's WAL stats into the global stats, then + * remove the entry from the dshash. + * + * Called at backend exit after the final flush, or when a ProcNumber is + * being reused. + */ +void +pgstat_wal_acc_backend_cb(void) +{ + pgstat_acc_my_per_backend(PGSTAT_KIND_WAL, &pgStatLocal.shmem->wal.lock); +} + +/* + * Returns per-backend WAL statistics for the given ProcNumber. + */ +PgStat_WalStats * +pgstat_fetch_stat_backend_wal(ProcNumber procnum) +{ + return (PgStat_WalStats *) pgstat_fetch_per_backend(PGSTAT_KIND_WAL, procnum); +} + +/* + * Reset a backend's WAL stats. Accumulate the entry's counters into the + * global stats, then zero the stats and set the reset timestamp. + */ +void +pgstat_wal_reset_backend_cb(ProcNumber procnum, TimestampTz ts) +{ + PgStatShared_Wal *shmem = &pgStatLocal.shmem->wal; + dshash_table *hash; + PgStatShared_WalBackendEntry *entry; + + hash = pgstat_per_backend_attach(PGSTAT_KIND_WAL); + + if (hash == NULL) + return; + + LWLockAcquire(&shmem->lock, LW_EXCLUSIVE); + + entry = dshash_find(hash, &procnum, true); + + if (entry == NULL) + { + LWLockRelease(&shmem->lock); + return; + } + + LWLockAcquire(&entry->header.lock, LW_EXCLUSIVE); + + /* Accumulate current stats into global before zeroing */ + WAL_ACCUMULATE_COUNTERS(shmem->stats.wal_counters, entry->stats.wal_counters); + + /* Zero stats and set reset timestamp */ + memset(&entry->stats.wal_counters, 0, sizeof(PgStat_WalCounters)); + entry->stats.stat_reset_timestamp = ts; + + LWLockRelease(&entry->header.lock); + dshash_release_lock(hash, entry); + LWLockRelease(&shmem->lock); +} + +/* + * Accumulate all per-backend WAL stats entries into the global stats and remove + * them. Called at clean server shutdown to ensure all flushed data is preserved + * in the stats file. + */ +void +pgstat_wal_acc_all_backends(void) +{ + pgstat_acc_all_per_backend(PGSTAT_KIND_WAL, &pgStatLocal.shmem->wal.lock); } diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 565d0e70768..5b0c819492a 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -1707,19 +1707,33 @@ Datum pg_stat_get_backend_wal(PG_FUNCTION_ARGS) { int pid; - PgStat_Backend *backend_stats; - PgStat_WalCounters bktype_stats; + PGPROC *proc; + ProcNumber procnum; + PgBackendStatus *beentry; + PgStat_WalStats *wal_stats; pid = PG_GETARG_INT32(0); - backend_stats = pgstat_fetch_stat_backend_by_pid(pid, NULL); - if (!backend_stats) + proc = BackendPidGetProc(pid); + + if (!proc) + proc = AuxiliaryPidGetProc(pid); + if (!proc) + PG_RETURN_NULL(); + + procnum = GetNumberFromPGProc(proc); + beentry = pgstat_get_beentry_by_proc_number(procnum); + + if (!beentry || beentry->st_procpid != pid) PG_RETURN_NULL(); - bktype_stats = backend_stats->wal_counters; + wal_stats = pgstat_fetch_stat_backend_wal(procnum); - /* save tuples with data from this PgStat_WalCounters */ - return (pg_stat_wal_build_tuple(bktype_stats, backend_stats->stat_reset_timestamp)); + if (!wal_stats) + PG_RETURN_NULL(); + + return (pg_stat_wal_build_tuple(wal_stats->wal_counters, + wal_stats->stat_reset_timestamp)); } /* @@ -2066,6 +2080,7 @@ pg_stat_reset_backend_stats(PG_FUNCTION_ARGS) PGPROC *proc; PgBackendStatus *beentry; ProcNumber procNumber; + TimestampTz ts; int backend_pid = PG_GETARG_INT32(0); proc = BackendPidGetProc(backend_pid); @@ -2087,6 +2102,14 @@ pg_stat_reset_backend_stats(PG_FUNCTION_ARGS) if (!pgstat_tracks_backend_bktype(beentry->st_backendType)) PG_RETURN_VOID(); + /* + * Accumulate the backend's WAL stats into the global stats, then zero the + * entry. + */ + ts = GetCurrentTimestamp(); + pgstat_wal_reset_backend_cb(procNumber, ts); + + /* Reset IO and Lock stats still in PGSTAT_KIND_BACKEND */ pgstat_reset(PGSTAT_KIND_BACKEND, InvalidOid, procNumber); PG_RETURN_VOID(); diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 58a44857f13..0ade7f2f053 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -522,7 +522,6 @@ typedef struct PgStat_Backend { TimestampTz stat_reset_timestamp; PgStat_BktypeIO io_stats; - PgStat_WalCounters wal_counters; PgStat_PendingLock lock_stats; } PgStat_Backend; @@ -842,6 +841,8 @@ extern void pgstat_execute_transactional_drops(int ndrops, struct xl_xact_stats_ extern void pgstat_report_wal(bool force); extern PgStat_WalStats *pgstat_fetch_stat_wal(void); +extern PgStat_WalStats *pgstat_fetch_stat_backend_wal(ProcNumber procnum); +extern void pgstat_wal_reset_backend_cb(ProcNumber procnum, TimestampTz ts); /* diff --git a/src/include/utils/pgstat_internal.h b/src/include/utils/pgstat_internal.h index 340244252c9..23ed4bdb4cc 100644 --- a/src/include/utils/pgstat_internal.h +++ b/src/include/utils/pgstat_internal.h @@ -510,11 +510,25 @@ typedef struct PgStatShared_PerBackendEntry LWLock lock; } PgStatShared_PerBackendEntry; +/* + * Per-backend entry for WAL statistics, stored in a dshash keyed by + * ProcNumber. + */ +typedef struct PgStatShared_WalBackendEntry +{ + PgStatShared_PerBackendEntry header; + PgStat_WalStats stats; +} PgStatShared_WalBackendEntry; + typedef struct PgStatShared_Wal { - /* lock protects ->stats */ LWLock lock; PgStat_WalStats stats; + + /* + * Per-backend dshash, keyed by ProcNumber. + */ + dshash_table_handle backend_hash_handle; } PgStatShared_Wal; @@ -749,9 +763,8 @@ extern void pgstat_archiver_snapshot_cb(void); /* flags for pgstat_flush_backend() */ #define PGSTAT_BACKEND_FLUSH_IO (1 << 0) /* Flush I/O statistics */ -#define PGSTAT_BACKEND_FLUSH_WAL (1 << 1) /* Flush WAL statistics */ #define PGSTAT_BACKEND_FLUSH_LOCK (1 << 2) /* Flush lock statistics */ -#define PGSTAT_BACKEND_FLUSH_ALL (PGSTAT_BACKEND_FLUSH_IO | PGSTAT_BACKEND_FLUSH_WAL | PGSTAT_BACKEND_FLUSH_LOCK) +#define PGSTAT_BACKEND_FLUSH_ALL (PGSTAT_BACKEND_FLUSH_IO | PGSTAT_BACKEND_FLUSH_LOCK) extern bool pgstat_flush_backend(bool nowait, uint32 flags); extern bool pgstat_backend_flush_cb(bool nowait); @@ -890,6 +903,9 @@ extern bool pgstat_wal_flush_cb(bool nowait); extern void pgstat_wal_init_shmem_cb(void *stats); extern void pgstat_wal_reset_all_cb(TimestampTz ts); extern void pgstat_wal_snapshot_cb(void); +extern void pgstat_wal_acc_backend_cb(void); +extern void pgstat_wal_acc_all_backends(void); +extern void pgstat_wal_per_backend_acc_cb(void *dst, void *entry); /* diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 0b7dbc42e80..5a4be198ada 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -2333,6 +2333,7 @@ PgStatShared_ReplSlot PgStatShared_SLRU PgStatShared_Subscription PgStatShared_Wal +PgStatShared_WalBackendEntry PgStat_ArchiverStats PgStat_Backend PgStat_BackendPending -- 2.34.1 --Hx3mnhNtand64WhS Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v1-0004-pgstat-move-Lock-statistics-to-new-per-backend-in.patch"