Hi hackers, PFA a patch for $SUBJECT.
Currently we can find xact_commit and xact_rollback in pg_stat_database but we don't have this information per backend. This patch adds 2 functions: pg_stat_get_backend_xact_commit() and pg_stat_get_backend_xact_rollback() to report the number of transactions that have been committed/rolled back for a given backend PID. I think having this information per-backend could be useful, for example, to: - check which application is producing the highest number of commit / rollback - check if the application's hosts have "uniform" commit/rollback pattern - check if some application's hosts are doing a lot of rollback (as compared to the other hosts): that could mean those hosts are not using an up-to-date application version This patch is pretty straightforward as it relies on the existing per backend statistics machinery that has been added in 9aea73fc61d (so that there is not that much design to discuss). On a side note, I noticed that when a transaction fails, say this way: postgres=# insert into bdt2 values(1); ERROR: relation "bdt2" does not exist Then the existing pg_stat_get_db_xact_rollback() does not return the rollback increment (so does pg_stat_database.xact_rollback). Indeed, the flush is done during the next commit or explicit rollback. Maybe we could add an extra counter, that tracks the transactions that have not been explicitly rolled back (xact_error or such) and flush it at the right time. Looking forward to your feedback, Regards, -- Bertrand Drouvot PostgreSQL Contributors Team RDS Open Source Databases Amazon Web Services: https://aws.amazon.com
>From 28747c241ac531fb54ca8cf3943e72fdbabb7d17 Mon Sep 17 00:00:00 2001 From: Bertrand Drouvot <bertranddrouvot...@gmail.com> Date: Mon, 4 Aug 2025 08:14:02 +0000 Subject: [PATCH v1] Adding per backend commit and rollback counters This commit adds 2 functions: pg_stat_get_backend_xact_commit() and pg_stat_get_backend_xact_rollback() to report the number of transactions that have been committed/rolled back for a given backend PID. It relies on the existing per backend statistics that has been added in 9aea73fc61d. --- doc/src/sgml/monitoring.sgml | 36 +++++++++++++ src/backend/utils/activity/pgstat_backend.c | 60 +++++++++++++++++++++ src/backend/utils/activity/pgstat_xact.c | 1 + src/backend/utils/adt/pgstatfuncs.c | 22 ++++++++ src/include/catalog/pg_proc.dat | 9 ++++ src/include/pgstat.h | 2 + src/include/utils/pgstat_internal.h | 4 +- src/test/regress/expected/stats.out | 17 ++++++ src/test/regress/sql/stats.sql | 10 ++++ 9 files changed, 160 insertions(+), 1 deletion(-) 26.9% doc/src/sgml/ 29.1% src/backend/utils/activity/ 12.3% src/backend/utils/adt/ 9.7% src/include/catalog/ 4.0% src/include/utils/ 9.2% src/test/regress/expected/ 7.3% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index fa78031ccbb..ef89683164f 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4921,6 +4921,42 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry id="pg-stat-get-backend-xact-commit" role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_xact_commit</primary> + </indexterm> + <function>pg_stat_get_backend_xact_commit</function> ( <type>integer</type> ) + <returnvalue>bigint</returnvalue> + </para> + <para> + Returns the number of transactions that have been committed by the backend + with the specified process ID. + </para> + <para> + The function does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para></entry> + </row> + + <row> + <entry id="pg-stat-get-backend-xact-rollback" role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_xact_rollback</primary> + </indexterm> + <function>pg_stat_get_backend_xact_rollback</function> ( <type>integer</type> ) + <returnvalue>bigint</returnvalue> + </para> + <para> + Returns the number of transactions that have been rolled back by the backend + with the specified process ID. + </para> + <para> + The function does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/utils/activity/pgstat_backend.c b/src/backend/utils/activity/pgstat_backend.c index 8714a85e2d9..7cec0c83071 100644 --- a/src/backend/utils/activity/pgstat_backend.c +++ b/src/backend/utils/activity/pgstat_backend.c @@ -47,6 +47,13 @@ static bool backend_has_iostats = false; */ static WalUsage prevBackendWalUsage; +/* + * For backend commit and rollback statistics. + */ +static int pgStatBackendXactCommit = 0; +static int pgStatBackendXactRollback = 0; +static bool backend_has_xactstats = false; + /* * Utility routines to report I/O stats for backends, kept here to avoid * exposing PendingBackendStats to the outside world. @@ -259,6 +266,33 @@ pgstat_flush_backend_entry_wal(PgStat_EntryRef *entry_ref) prevBackendWalUsage = pgWalUsage; } +/* + * Flush out locally pending backend xact statistics. Locking is managed + * by the caller. + */ +static void +pgstat_flush_backend_entry_xact(PgStat_EntryRef *entry_ref) +{ + PgStatShared_Backend *shbackendent; + + /* + * This function can be called even if nothing at all has happened for + * XACT statistics. In this case, avoid unnecessarily modifying the stats + * entry. + */ + if (!backend_has_xactstats) + return; + + shbackendent = (PgStatShared_Backend *) entry_ref->shared_stats; + + shbackendent->stats.xact_commit += pgStatBackendXactCommit; + shbackendent->stats.xact_rollback += pgStatBackendXactRollback; + + pgStatBackendXactCommit = pgStatBackendXactRollback = 0; + + backend_has_xactstats = false; +} + /* * Flush out locally pending backend statistics * @@ -283,6 +317,10 @@ pgstat_flush_backend(bool nowait, bits32 flags) pgstat_backend_wal_have_pending()) has_pending_data = true; + /* Some XACT data pending? */ + if ((flags & PGSTAT_BACKEND_FLUSH_XACT) && backend_has_xactstats) + has_pending_data = true; + if (!has_pending_data) return false; @@ -298,6 +336,9 @@ pgstat_flush_backend(bool nowait, bits32 flags) if (flags & PGSTAT_BACKEND_FLUSH_WAL) pgstat_flush_backend_entry_wal(entry_ref); + if (flags & PGSTAT_BACKEND_FLUSH_XACT) + pgstat_flush_backend_entry_xact(entry_ref); + pgstat_unlock_entry(entry_ref); return false; @@ -400,3 +441,22 @@ pgstat_backend_reset_timestamp_cb(PgStatShared_Common *header, TimestampTz ts) { ((PgStatShared_Backend *) header)->stats.stat_reset_timestamp = ts; } + +void +AtEOXact_PgStat_Backend(bool isCommit, bool parallel) +{ + /* Don't count parallel worker transaction stats */ + if (!parallel) + { + /* + * Count transaction commit or abort. (We use counters, not just + * bools, in case the reporting message isn't sent right away.) + */ + if (isCommit) + pgStatBackendXactCommit++; + else + pgStatBackendXactRollback++; + + backend_has_xactstats = true; + } +} diff --git a/src/backend/utils/activity/pgstat_xact.c b/src/backend/utils/activity/pgstat_xact.c index bc9864bd8d9..cd1c501c165 100644 --- a/src/backend/utils/activity/pgstat_xact.c +++ b/src/backend/utils/activity/pgstat_xact.c @@ -42,6 +42,7 @@ AtEOXact_PgStat(bool isCommit, bool parallel) PgStat_SubXactStatus *xact_state; AtEOXact_PgStat_Database(isCommit, parallel); + AtEOXact_PgStat_Backend(isCommit, parallel); /* handle transactional stats information */ xact_state = pgStatXactStack; diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..6436129f516 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -1606,6 +1606,28 @@ pg_stat_get_backend_io(PG_FUNCTION_ARGS) return (Datum) 0; } +#define PG_STAT_GET_BACKENDENTRY_INT64(stat) \ +Datum \ +CppConcat(pg_stat_get_backend_,stat)(PG_FUNCTION_ARGS) \ +{ \ + int pid; \ + PgStat_Backend *backend_stats; \ + \ + pid = PG_GETARG_INT32(0); \ + backend_stats = pgstat_fetch_stat_backend_by_pid(pid, NULL);\ + \ + if (!backend_stats) \ + PG_RETURN_NULL(); \ + else \ + PG_RETURN_INT64(backend_stats->stat); \ +} + +/* pg_stat_get_backend_xact_commit */ +PG_STAT_GET_BACKENDENTRY_INT64(xact_commit) + +/* pg_stat_get_backend_xact_rollback */ +PG_STAT_GET_BACKENDENTRY_INT64(xact_rollback) + /* * pg_stat_wal_build_tuple * diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..f5085d4e016 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -6010,6 +6010,15 @@ proargnames => '{backend_pid,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}', prosrc => 'pg_stat_get_backend_io' }, +{ oid => '8170', descr => 'statistics: backend transactions committed', + proname => 'pg_stat_get_backend_xact_commit', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'int4', + prosrc => 'pg_stat_get_backend_xact_commit' }, +{ oid => '8916', descr => 'statistics: backend transactions rolled back', + proname => 'pg_stat_get_backend_xact_rollback', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'int4', + prosrc => 'pg_stat_get_backend_xact_rollback' }, + { oid => '1136', descr => 'statistics: information about WAL activity', proname => 'pg_stat_get_wal', proisstrict => 'f', provolatile => 's', proparallel => 'r', prorettype => 'record', proargtypes => '', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 202bd2d5ace..4c9f6b0dcec 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -490,6 +490,8 @@ typedef struct PgStat_Backend TimestampTz stat_reset_timestamp; PgStat_BktypeIO io_stats; PgStat_WalCounters wal_counters; + PgStat_Counter xact_commit; + PgStat_Counter xact_rollback; } PgStat_Backend; /* --------- diff --git a/src/include/utils/pgstat_internal.h b/src/include/utils/pgstat_internal.h index 6cf00008f63..23ea8ffd618 100644 --- a/src/include/utils/pgstat_internal.h +++ b/src/include/utils/pgstat_internal.h @@ -616,12 +616,14 @@ 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_ALL (PGSTAT_BACKEND_FLUSH_IO | PGSTAT_BACKEND_FLUSH_WAL) +#define PGSTAT_BACKEND_FLUSH_XACT (1 << 2) /* Flush xact statistics */ +#define PGSTAT_BACKEND_FLUSH_ALL (PGSTAT_BACKEND_FLUSH_IO | PGSTAT_BACKEND_FLUSH_WAL | PGSTAT_BACKEND_FLUSH_XACT) extern bool pgstat_flush_backend(bool nowait, bits32 flags); extern bool pgstat_backend_flush_cb(bool nowait); extern void pgstat_backend_reset_timestamp_cb(PgStatShared_Common *header, TimestampTz ts); +extern void AtEOXact_PgStat_Backend(bool isCommit, bool parallel); /* * Functions in pgstat_bgwriter.c diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..0d316f94e40 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT pg_stat_get_backend_xact_commit AS xact_commit_before + FROM pg_stat_get_backend_xact_commit(pg_backend_pid()) \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT pg_stat_get_backend_xact_commit AS xact_commit_after + FROM pg_stat_get_backend_xact_commit(pg_backend_pid()) \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..d629140d880 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT pg_stat_get_backend_xact_commit AS xact_commit_before + FROM pg_stat_get_backend_xact_commit(pg_backend_pid()) \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT pg_stat_get_backend_xact_commit AS xact_commit_after + FROM pg_stat_get_backend_xact_commit(pg_backend_pid()) \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1