1
0
mirror of https://github.com/postgres/postgres.git synced 2025-06-01 14:21:49 +03:00

Add WAL data to backend statistics

This commit adds per-backend WAL statistics, providing the same
information as pg_stat_wal, except that it is now possible to know how
much WAL activity is happening in each backend rather than an overall
aggregate of all the activity.  Like pg_stat_wal, the implementation
relies on pgWalUsage, tracking the difference of activity between two
reports to pgstats.

This data can be retrieved with a new system function called
pg_stat_get_backend_wal(), that returns one tuple based on the PID
provided in input.  Like pg_stat_get_backend_io(), this is useful when
joined with pg_stat_activity to get a live picture of the WAL generated
for each running backend, showing how the activity is [un]balanced.

pgstat_flush_backend() gains a new flag value, able to control the flush
of the WAL stats.

This commit relies mostly on the infrastructure provided by
9aea73fc61d4, that has introduced backend statistics.

Bump catalog version.  A bump of PGSTAT_FILE_FORMAT_ID is not required,
as backend stats do not persist on disk.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Michael Paquier <michael@paquier.xyz>
Reviewed-by: Nazir Bilal Yavuz <byavuz81@gmail.com>
Reviewed-by: Xuneng Zhou <xunengzhou@gmail.com>
Discussion: https://postgr.es/m/Z3zqc4o09dM/Ezyz@ip-10-97-1-34.eu-west-3.compute.internal
This commit is contained in:
Michael Paquier 2025-03-11 09:04:11 +09:00
parent 59a1592e39
commit 76def4cdd7
10 changed files with 185 additions and 27 deletions

View File

@ -4866,6 +4866,25 @@ description | Waiting for a newly initialized WAL file to reach durable storage
</para></entry>
</row>
<row>
<entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature">
<indexterm>
<primary>pg_stat_get_backend_wal</primary>
</indexterm>
<function>pg_stat_get_backend_wal</function> ( <type>integer</type> )
<returnvalue>record</returnvalue>
</para>
<para>
Returns WAL statistics about the backend with the specified
process ID. The output fields are exactly the same as the ones in the
<structname>pg_stat_wal</structname> view.
</para>
<para>
The function does not return WAL statistics for the checkpointer,
the background writer, the startup process and the autovacuum launcher.
</para></entry>
</row>
<row>
<entry role="func_table_entry"><para role="func_signature">
<indexterm>

View File

@ -38,6 +38,14 @@
*/
static PgStat_BackendPending PendingBackendStats = {0};
/*
* WAL usage counters saved from pgWalUsage at the previous call to
* pgstat_report_wal(). This is used to calculate how much WAL usage
* happens between pgstat_report_wal() 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.
@ -184,6 +192,57 @@ pgstat_flush_backend_entry_io(PgStat_EntryRef *entry_ref)
MemSet(&PendingBackendStats.pending_io, 0, sizeof(PgStat_PendingIO));
}
/*
* 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);
#undef WALSTAT_ACC
/*
* Save the current counters for the subsequent calculation of WAL usage.
*/
prevBackendWalUsage = pgWalUsage;
}
/*
* Flush out locally pending backend statistics
*
@ -194,12 +253,23 @@ bool
pgstat_flush_backend(bool nowait, bits32 flags)
{
PgStat_EntryRef *entry_ref;
bool has_pending_data = false;
if (!pgstat_tracks_backend_bktype(MyBackendType))
return false;
if (pg_memory_is_all_zeros(&PendingBackendStats,
sizeof(struct PgStat_BackendPending)))
/* Some IO data pending? */
if ((flags & PGSTAT_BACKEND_FLUSH_IO) &&
!pg_memory_is_all_zeros(&PendingBackendStats.pending_io,
sizeof(struct PgStat_PendingIO)))
has_pending_data = true;
/* Some WAL data pending? */
if ((flags & PGSTAT_BACKEND_FLUSH_WAL) &&
pgstat_backend_wal_have_pending())
has_pending_data = true;
if (!has_pending_data)
return false;
entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_BACKEND, InvalidOid,
@ -211,6 +281,9 @@ pgstat_flush_backend(bool nowait, bits32 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);
pgstat_unlock_entry(entry_ref);
return false;
@ -226,7 +299,8 @@ pgstat_backend_have_pending_cb(void)
return false;
return (!pg_memory_is_all_zeros(&PendingBackendStats,
sizeof(struct PgStat_BackendPending)));
sizeof(struct PgStat_BackendPending)) ||
pgstat_backend_wal_have_pending());
}
/*
@ -261,6 +335,13 @@ pgstat_create_backend(ProcNumber procnum)
pgstat_unlock_entry(entry_ref);
MemSet(&PendingBackendStats, 0, sizeof(PgStat_BackendPending));
/*
* 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;
}
/*

View File

@ -52,6 +52,7 @@ 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);

View File

@ -1609,8 +1609,8 @@ pg_stat_get_backend_io(PG_FUNCTION_ARGS)
/*
* pg_stat_wal_build_tuple
*
* Helper routine for pg_stat_get_wal() returning one tuple based on the
* contents of wal_counters.
* Helper routine for pg_stat_get_wal() and pg_stat_get_backend_wal()
* returning one tuple based on the contents of wal_counters.
*/
static Datum
pg_stat_wal_build_tuple(PgStat_WalCounters wal_counters,
@ -1659,6 +1659,28 @@ pg_stat_wal_build_tuple(PgStat_WalCounters wal_counters,
PG_RETURN_DATUM(HeapTupleGetDatum(heap_form_tuple(tupdesc, values, nulls)));
}
/*
* Returns WAL statistics for a backend with given PID.
*/
Datum
pg_stat_get_backend_wal(PG_FUNCTION_ARGS)
{
int pid;
PgStat_Backend *backend_stats;
PgStat_WalCounters bktype_stats;
pid = PG_GETARG_INT32(0);
backend_stats = pgstat_fetch_stat_backend_by_pid(pid, NULL);
if (!backend_stats)
PG_RETURN_NULL();
bktype_stats = backend_stats->wal_counters;
/* save tuples with data from this PgStat_WalCounters */
return (pg_stat_wal_build_tuple(bktype_stats, backend_stats->stat_reset_timestamp));
}
/*
* Returns statistics of WAL activity
*/

View File

@ -57,6 +57,6 @@
*/
/* yyyymmddN */
#define CATALOG_VERSION_NO 202503071
#define CATALOG_VERSION_NO 202503111
#endif

View File

@ -5973,6 +5973,13 @@
proargmodes => '{o,o,o,o,o}',
proargnames => '{wal_records,wal_fpi,wal_bytes,wal_buffers_full,stats_reset}',
prosrc => 'pg_stat_get_wal' },
{ oid => '8037', descr => 'statistics: backend WAL activity',
proname => 'pg_stat_get_backend_wal', provolatile => 'v', proparallel => 'r',
prorettype => 'record', proargtypes => 'int4',
proallargtypes => '{int4,int8,int8,numeric,int8,timestamptz}',
proargmodes => '{i,o,o,o,o,o}',
proargnames => '{backend_pid,wal_records,wal_fpi,wal_bytes,wal_buffers_full,stats_reset}',
prosrc => 'pg_stat_get_backend_wal' },
{ oid => '6248', descr => 'statistics: information about WAL prefetching',
proname => 'pg_stat_get_recovery_prefetch', prorows => '1', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => '',

View File

@ -340,24 +340,6 @@ typedef struct PgStat_IO
PgStat_BktypeIO stats[BACKEND_NUM_TYPES];
} PgStat_IO;
typedef struct PgStat_Backend
{
TimestampTz stat_reset_timestamp;
PgStat_BktypeIO io_stats;
} PgStat_Backend;
/* ---------
* PgStat_BackendPending Non-flushed backend stats.
* ---------
*/
typedef struct PgStat_BackendPending
{
/*
* Backend statistics store the same amount of IO data as PGSTAT_KIND_IO.
*/
PgStat_PendingIO pending_io;
} PgStat_BackendPending;
typedef struct PgStat_StatDBEntry
{
PgStat_Counter xact_commit;
@ -500,6 +482,29 @@ typedef struct PgStat_WalStats
TimestampTz stat_reset_timestamp;
} PgStat_WalStats;
/* -------
* PgStat_Backend Backend statistics
* -------
*/
typedef struct PgStat_Backend
{
TimestampTz stat_reset_timestamp;
PgStat_BktypeIO io_stats;
PgStat_WalCounters wal_counters;
} PgStat_Backend;
/* ---------
* PgStat_BackendPending Non-flushed backend stats.
* ---------
*/
typedef struct PgStat_BackendPending
{
/*
* Backend statistics store the same amount of IO data as PGSTAT_KIND_IO.
*/
PgStat_PendingIO pending_io;
} PgStat_BackendPending;
/*
* Functions in pgstat.c
*/

View File

@ -622,7 +622,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_ALL (PGSTAT_BACKEND_FLUSH_IO)
#define PGSTAT_BACKEND_FLUSH_WAL (1 << 1) /* Flush WAL statistics */
#define PGSTAT_BACKEND_FLUSH_ALL (PGSTAT_BACKEND_FLUSH_IO | PGSTAT_BACKEND_FLUSH_WAL)
extern bool pgstat_flush_backend(bool nowait, bits32 flags);
extern bool pgstat_backend_flush_cb(bool nowait);

View File

@ -908,8 +908,11 @@ SELECT sessions > :db_stat_sessions FROM pg_stat_database WHERE datname = (SELEC
-- Test pg_stat_checkpointer checkpointer-related stats, together with pg_stat_wal
SELECT num_requested AS rqst_ckpts_before FROM pg_stat_checkpointer \gset
-- Test pg_stat_wal (and make a temp table so our temp schema exists)
-- Test pg_stat_wal
SELECT wal_bytes AS wal_bytes_before FROM pg_stat_wal \gset
-- Test pg_stat_get_backend_wal()
SELECT wal_bytes AS backend_wal_bytes_before from pg_stat_get_backend_wal(pg_backend_pid()) \gset
-- Make a temp table so our temp schema exists
CREATE TEMP TABLE test_stats_temp AS SELECT 17;
DROP TABLE test_stats_temp;
-- Checkpoint twice: The checkpointer reports stats after reporting completion
@ -929,6 +932,18 @@ SELECT wal_bytes > :wal_bytes_before FROM pg_stat_wal;
t
(1 row)
SELECT pg_stat_force_next_flush();
pg_stat_force_next_flush
--------------------------
(1 row)
SELECT wal_bytes > :backend_wal_bytes_before FROM pg_stat_get_backend_wal(pg_backend_pid());
?column?
----------
t
(1 row)
-- Test pg_stat_get_backend_idset() and some allied functions.
-- In particular, verify that their notion of backend ID matches
-- our temp schema index.

View File

@ -426,9 +426,13 @@ SELECT sessions > :db_stat_sessions FROM pg_stat_database WHERE datname = (SELEC
-- Test pg_stat_checkpointer checkpointer-related stats, together with pg_stat_wal
SELECT num_requested AS rqst_ckpts_before FROM pg_stat_checkpointer \gset
-- Test pg_stat_wal (and make a temp table so our temp schema exists)
-- Test pg_stat_wal
SELECT wal_bytes AS wal_bytes_before FROM pg_stat_wal \gset
-- Test pg_stat_get_backend_wal()
SELECT wal_bytes AS backend_wal_bytes_before from pg_stat_get_backend_wal(pg_backend_pid()) \gset
-- Make a temp table so our temp schema exists
CREATE TEMP TABLE test_stats_temp AS SELECT 17;
DROP TABLE test_stats_temp;
@ -441,6 +445,9 @@ CHECKPOINT;
SELECT num_requested > :rqst_ckpts_before FROM pg_stat_checkpointer;
SELECT wal_bytes > :wal_bytes_before FROM pg_stat_wal;
SELECT pg_stat_force_next_flush();
SELECT wal_bytes > :backend_wal_bytes_before FROM pg_stat_get_backend_wal(pg_backend_pid());
-- Test pg_stat_get_backend_idset() and some allied functions.
-- In particular, verify that their notion of backend ID matches
-- our temp schema index.