]> git.ipfire.org Git - thirdparty/postgresql.git/commitdiff
Add backend-level lock statistics
authorMichael Paquier <michael@paquier.xyz>
Tue, 30 Jun 2026 07:59:20 +0000 (16:59 +0900)
committerMichael Paquier <michael@paquier.xyz>
Tue, 30 Jun 2026 07:59:20 +0000 (16:59 +0900)
This commit adds per-backend lock statistics, providing the same
information as pg_stat_lock.  It is now possible to retrieve those stats
(lock wait counts, wait times, and fast-path exceeded count) on a
per-backend basis.

This data can be retrieved with a new system function called
pg_stat_get_backend_lock(), that returns one tuple per lock type based
on the PID provided in input.  Like pg_stat_get_backend_io(), this is
useful if joined with pg_stat_activity to get a live picture of the
locks behavior for each running backend.

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

This commit is straight-forward, relying on the infrastructure provided
by 9aea73fc61d4 (backend-level pgstats).

Bump catalog version.  No need to touch PGSTAT_FILE_FORMAT_ID as backend
statistics are never written to disk.

Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Tatsuya Kawata <kawatatatsuya0913@gmail.com>
Reviewed-by: Michael Paquier <michael@paquier.xyz>
Reviewed-by: Tristan Partin <tristan@partin.io>
Reviewed-by: Rui Zhao <zhaorui126@gmail.com>
Discussion: https://postgr.es/m/aiAzEY+cMQb/W8yu@bdtpg

doc/src/sgml/monitoring.sgml
src/backend/utils/activity/pgstat_backend.c
src/backend/utils/activity/pgstat_lock.c
src/backend/utils/adt/pgstatfuncs.c
src/include/catalog/catversion.h
src/include/catalog/pg_proc.dat
src/include/pgstat.h
src/include/utils/pgstat_internal.h
src/test/regress/expected/stats.out
src/test/regress/sql/stats.sql

index 6dcf05eb7020f97055e9031049eed356a2ba0daf..05bd501c6822a046f475e345dd74219592366ac4 100644 (file)
@@ -5526,6 +5526,25 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry id="pg-stat-get-backend-lock" role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_lock</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_lock</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns lock statistics about the backend with the specified
+        process ID. The output fields are exactly the same as the ones in the
+        <structname>pg_stat_lock</structname> view.
+       </para>
+       <para>
+        The function does not return lock 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>
index 73461c9bca5907114e570e9240d6ead25a1c5500..b736b2ccc6ff304dd2ad5efa458209180eec83d6 100644 (file)
@@ -39,6 +39,7 @@
  */
 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
@@ -86,6 +87,39 @@ pgstat_count_backend_io_op(IOObject io_object, IOContext io_context,
        pgstat_report_fixed = true;
 }
 
+/*
+ * Utility routines to report lock stats for backends, kept here to avoid
+ * exposing PendingBackendStats to the outside world.
+ */
+void
+pgstat_count_backend_lock_waits(uint8 locktag_type, PgStat_Counter usecs)
+{
+       if (!pgstat_tracks_backend_bktype(MyBackendType))
+               return;
+
+       Assert(locktag_type <= LOCKTAG_LAST_TYPE);
+
+       PendingBackendStats.pending_lock.stats[locktag_type].waits++;
+       PendingBackendStats.pending_lock.stats[locktag_type].wait_time += usecs;
+
+       backend_has_lockstats = true;
+       pgstat_report_fixed = true;
+}
+
+void
+pgstat_count_backend_lock_fastpath_exceeded(uint8 locktag_type)
+{
+       if (!pgstat_tracks_backend_bktype(MyBackendType))
+               return;
+
+       Assert(locktag_type <= LOCKTAG_LAST_TYPE);
+
+       PendingBackendStats.pending_lock.stats[locktag_type].fastpath_exceeded++;
+
+       backend_has_lockstats = true;
+       pgstat_report_fixed = true;
+}
+
 /*
  * Returns statistics of a backend by proc number.
  */
@@ -262,6 +296,36 @@ pgstat_flush_backend_entry_wal(PgStat_EntryRef *entry_ref)
        prevBackendWalUsage = pgWalUsage;
 }
 
+/*
+ * Flush out locally pending backend lock statistics.  Locking is managed
+ * by the caller.
+ */
+static void
+pgstat_flush_backend_entry_lock(PgStat_EntryRef *entry_ref)
+{
+       PgStatShared_Backend *shbackendent;
+       PgStat_PendingLock *bktype_shstats;
+
+       if (!backend_has_lockstats)
+               return;
+
+       shbackendent = (PgStatShared_Backend *) entry_ref->shared_stats;
+       bktype_shstats = &shbackendent->stats.lock_stats;
+
+       for (int i = 0; i <= LOCKTAG_LAST_TYPE; i++)
+       {
+#define LOCKSTAT_ACC(fld) \
+       (bktype_shstats->stats[i].fld += PendingBackendStats.pending_lock.stats[i].fld)
+               LOCKSTAT_ACC(waits);
+               LOCKSTAT_ACC(wait_time);
+               LOCKSTAT_ACC(fastpath_exceeded);
+#undef LOCKSTAT_ACC
+       }
+
+       MemSet(&PendingBackendStats.pending_lock, 0, sizeof(PgStat_PendingLock));
+       backend_has_lockstats = false;
+}
+
 /*
  * Flush out locally pending backend statistics
  *
@@ -286,6 +350,10 @@ pgstat_flush_backend(bool nowait, uint32 flags)
                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;
+
        if (!has_pending_data)
                return false;
 
@@ -301,6 +369,9 @@ pgstat_flush_backend(bool nowait, uint32 flags)
        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);
+
        pgstat_unlock_entry(entry_ref);
 
        return false;
@@ -339,6 +410,7 @@ 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
index 8910a15634dd13e43956fa47c02feed34d7eddc0..c20c7599683cc9ac0b5e14fe53425d5632cd5720 100644 (file)
@@ -131,6 +131,8 @@ pgstat_count_lock_fastpath_exceeded(uint8 locktag_type)
        PendingLockStats.stats[locktag_type].fastpath_exceeded++;
        have_lockstats = true;
        pgstat_report_fixed = true;
+
+       pgstat_count_backend_lock_fastpath_exceeded(locktag_type);
 }
 
 /*
@@ -147,4 +149,6 @@ pgstat_count_lock_waits(uint8 locktag_type, PgStat_Counter usecs)
        PendingLockStats.stats[locktag_type].wait_time += usecs;
        have_lockstats = true;
        pgstat_report_fixed = true;
+
+       pgstat_count_backend_lock_waits(locktag_type, usecs);
 }
index 1f9165f1616054cc68c696b77cd3bd54a8b22afc..0c6a20843a501b25ad1233c659e681de699fbce7 100644 (file)
@@ -1740,8 +1740,8 @@ pg_stat_get_wal(PG_FUNCTION_ARGS)
 /*
  * pg_stat_lock_build_tuples
  *
- * Helper routine for pg_stat_get_lock(), filling a result tuplestore with one
- * tuple for each lock type.
+ * Helper routine for pg_stat_get_lock() and pg_stat_get_backend_lock(),
+ * filling a result tuplestore with one tuple for each lock type.
  */
 static void
 pg_stat_lock_build_tuples(ReturnSetInfo *rsinfo,
@@ -1788,6 +1788,31 @@ pg_stat_get_lock(PG_FUNCTION_ARGS)
        return (Datum) 0;
 }
 
+/*
+ * Returns lock statistics for a backend with given PID.
+ */
+Datum
+pg_stat_get_backend_lock(PG_FUNCTION_ARGS)
+{
+       int                     pid;
+       ReturnSetInfo *rsinfo;
+       PgStat_Backend *backend_stats;
+
+       InitMaterializedSRF(fcinfo, 0);
+       rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+       pid = PG_GETARG_INT32(0);
+       backend_stats = pgstat_fetch_stat_backend_by_pid(pid, NULL);
+
+       if (!backend_stats)
+               return (Datum) 0;
+
+       pg_stat_lock_build_tuples(rsinfo, backend_stats->lock_stats.stats,
+                                                         backend_stats->stat_reset_timestamp);
+
+       return (Datum) 0;
+}
+
 /*
  * Returns statistics of SLRU caches.
  */
index 875a147f7533f2d01c0c2e5c3e0daf78048798d7..e06eb2abbcbbc8750105f76aa32aa3178bef383b 100644 (file)
@@ -57,6 +57,6 @@
  */
 
 /*                                                     yyyymmddN */
-#define CATALOG_VERSION_NO     202606301
+#define CATALOG_VERSION_NO     202606302
 
 #endif
index efe13b7866a194b456aff4e3a2adf28e5c23b120..73bb7fbb4304fddb23aec4faa35e7d2c446f2319 100644 (file)
   proargmodes => '{i,o,o,o,o,o,o}',
   proargnames => '{backend_pid,wal_records,wal_fpi,wal_bytes,wal_fpi_bytes,wal_buffers_full,stats_reset}',
   prosrc => 'pg_stat_get_backend_wal' },
+{ oid => '9682', descr => 'statistics: backend lock statistics',
+  proname => 'pg_stat_get_backend_lock', prorows => '10', proretset => 't',
+  provolatile => 'v', proparallel => 'r', prorettype => 'record',
+  proargtypes => 'int4',
+  proallargtypes => '{int4,text,int8,float8,int8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o}',
+  proargnames => '{backend_pid,locktype,waits,wait_time,fastpath_exceeded,stats_reset}',
+  prosrc => 'pg_stat_get_backend_lock' },
 { oid => '6248', descr => 'statistics: information about WAL prefetching',
   proname => 'pg_stat_get_recovery_prefetch', prorows => '1', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
index 724966959998f94e9fa3988421135b3d418f4ced..58a44857f1311ba665508a7901651bcb480ca1ac 100644 (file)
@@ -523,6 +523,7 @@ typedef struct PgStat_Backend
        TimestampTz stat_reset_timestamp;
        PgStat_BktypeIO io_stats;
        PgStat_WalCounters wal_counters;
+       PgStat_PendingLock lock_stats;
 } PgStat_Backend;
 
 /* ---------
@@ -535,6 +536,12 @@ typedef struct PgStat_BackendPending
         * Backend statistics store the same amount of IO data as PGSTAT_KIND_IO.
         */
        PgStat_PendingIO pending_io;
+
+       /*
+        * Backend statistics store the same amount of lock data as
+        * PGSTAT_KIND_LOCK.
+        */
+       PgStat_PendingLock pending_lock;
 } PgStat_BackendPending;
 
 /*
@@ -586,6 +593,11 @@ extern void pgstat_count_backend_io_op(IOObject io_object,
                                                                           IOContext io_context,
                                                                           IOOp io_op, uint32 cnt,
                                                                           uint64 bytes);
+
+/* used by pgstat_lock.c for lock stats tracked in backends */
+extern void pgstat_count_backend_lock_waits(uint8 locktag_type, PgStat_Counter usecs);
+extern void pgstat_count_backend_lock_fastpath_exceeded(uint8 locktag_type);
+
 extern PgStat_Backend *pgstat_fetch_stat_backend(ProcNumber procNumber);
 extern PgStat_Backend *pgstat_fetch_stat_backend_by_pid(int pid,
                                                                                                                BackendType *bktype);
index 3ca4f454895687be5d870e5b33e701dc8ab495f8..b3dc3ff7d8bb74683a7feb3997a4b7033e09ef91 100644 (file)
@@ -705,7 +705,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_ALL   (PGSTAT_BACKEND_FLUSH_IO | PGSTAT_BACKEND_FLUSH_WAL)
+#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)
 
 extern bool pgstat_flush_backend(bool nowait, uint32 flags);
 extern bool pgstat_backend_flush_cb(bool nowait);
index bbb1db3c4334737e692e1c9ce45fddac239bcde7..fa550676f8355c7c467033dbc7c4b36d83bf2de3 100644 (file)
@@ -2019,6 +2019,10 @@ BEGIN
 END;
 $$;
 SELECT fastpath_exceeded AS fastpath_exceeded_before FROM pg_stat_lock WHERE locktype = 'relation' \gset
+-- Test pg_stat_get_backend_lock()
+SELECT fastpath_exceeded AS backend_fastpath_exceeded_before
+  FROM pg_stat_get_backend_lock(pg_backend_pid())
+  WHERE locktype = 'relation' \gset
 -- Needs a lock on each partition
 SELECT count(*) FROM part_test;
  count 
@@ -2039,5 +2043,13 @@ SELECT fastpath_exceeded > :fastpath_exceeded_before FROM pg_stat_lock WHERE loc
  t
 (1 row)
 
+SELECT fastpath_exceeded > :backend_fastpath_exceeded_before
+  FROM pg_stat_get_backend_lock(pg_backend_pid())
+  WHERE locktype = 'relation';
+ ?column? 
+----------
+ t
+(1 row)
+
 DROP TABLE part_test;
 -- End of Stats Test
index 610fd21fae447cb8222a053c1d673dfb4f56a9ef..f5683302a75c0ea5f067ebc818cbd1664e13e899 100644 (file)
@@ -998,6 +998,11 @@ $$;
 
 SELECT fastpath_exceeded AS fastpath_exceeded_before FROM pg_stat_lock WHERE locktype = 'relation' \gset
 
+-- Test pg_stat_get_backend_lock()
+SELECT fastpath_exceeded AS backend_fastpath_exceeded_before
+  FROM pg_stat_get_backend_lock(pg_backend_pid())
+  WHERE locktype = 'relation' \gset
+
 -- Needs a lock on each partition
 SELECT count(*) FROM part_test;
 
@@ -1006,6 +1011,10 @@ SELECT pg_stat_force_next_flush();
 
 SELECT fastpath_exceeded > :fastpath_exceeded_before FROM pg_stat_lock WHERE locktype = 'relation';
 
+SELECT fastpath_exceeded > :backend_fastpath_exceeded_before
+  FROM pg_stat_get_backend_lock(pg_backend_pid())
+  WHERE locktype = 'relation';
+
 DROP TABLE part_test;
 
 -- End of Stats Test