Hi hackers, Please find attached a patch to add a new view (namely pg_stat_lock) that provides lock statistics.
It’s output is like the following: postgres=# select * from pg_stat_lock; locktype | requests | waits | timeouts | deadlock_timeouts | deadlocks | fastpath | stats_reset ------------------+----------+-------+----------+-------------------+-----------+----------+------------------------------- relation | 612775 | 1 | 0 | 0 | 0 | 531115 | 2025-08-01 09:18:26.476275+00 extend | 3128 | 0 | 0 | 0 | 0 | 0 | 2025-08-01 09:18:26.476275+00 frozenid | 11 | 0 | 0 | 0 | 0 | 0 | 2025-08-01 09:18:26.476275+00 page | 1 | 0 | 0 | 0 | 0 | 0 | 2025-08-01 09:18:26.476275+00 tuple | 3613 | 0 | 0 | 0 | 0 | 0 | 2025-08-01 09:18:26.476275+00 transactionid | 6130 | 0 | 0 | 0 | 0 | 0 | 2025-08-01 09:18:26.476275+00 virtualxid | 15390 | 0 | 0 | 0 | 0 | 15390 | 2025-08-01 09:18:26.476275+00 spectoken | 12 | 0 | 0 | 0 | 0 | 0 | 2025-08-01 09:18:26.476275+00 object | 8393 | 0 | 0 | 0 | 0 | 0 | 2025-08-01 09:18:26.476275+00 userlock | 0 | 0 | 0 | 0 | 0 | 0 | 2025-08-01 09:18:26.476275+00 advisory | 44 | 0 | 0 | 0 | 0 | 0 | 2025-08-01 09:18:26.476275+00 applytransaction | 0 | 0 | 0 | 0 | 0 | 0 | 2025-08-01 09:18:26.476275+00 It means that it provides historical trends of locks usage per lock type. It can be used for example for: 1. checking if "waits" is close to "requests". Then it means you usually have to wait before acquiring the lock, which means you may have a concurrency issue. 2. lock_timeout and deadlock_timeout tuning (lock_timeout is visible only in the logs if log_min_error_statement is set appropriately). 3. checking the "requests"/"fastpath" ratio to see if "max_locks_per_transaction" needs tuning (see c4d5cb71d2). If any points need more details, it might be a good idea to start sampling pg_locks. The patch is made of 2 sub-patches: 0001 - It adds a new stat kind PGSTAT_KIND_LOCK for the lock statistics. This new statistic kind is a fixed one because its key is the lock type so that we know its size is LOCKTAG_LAST_TYPE + 1. This statistic kind records the following counters: - requests: Number of requests for this lock type. - waits: Number of times requests for this lock type had to wait. - timeouts: Number of times requests for this lock type had to wait longer than lock_timeout. - deadlock_timeouts: Number of times requests for this lock type had to wait longer than deadlock_timeout. - deadlocks: Number of times a deadlock occurred on this lock type. - fastpath: Number of times this lock type was taken via fast path. No extra details is added (like the ones, i.e relation oid, database oid, we can find in pg_locks). The idea is to provide an idea on what the locking behaviour looks like. Those new counters are incremented outside of the wait events code path, as suggested in [1]. There are no major design choices, it relies on the current statistics machinery. 0002 - It adds the pg_stat_lock view It also adds documentation and some tests. Remarks: - maybe we could add some metrics related to the lock duration (we have some hints thanks to the timeout ounters though) - if this is merged, a next step could be to record those metrics per backend [1]: https://www.postgresql.org/message-id/CA%2BTgmobptuUWo7X5zcQrWKh22qeAn4eL%2B%3Dwtb8_ajCOR%2B7_tcw%40mail.gmail.com Looking forward to your feedback, Regards, -- Bertrand Drouvot PostgreSQL Contributors Team RDS Open Source Databases Amazon Web Services: https://aws.amazon.com
>From 18a9a9ca8fefa25c1afcf676765576346c3e7fe7 Mon Sep 17 00:00:00 2001 From: Bertrand Drouvot <bertranddrouvot...@gmail.com> Date: Tue, 29 Jul 2025 08:36:35 +0000 Subject: [PATCH v1 1/2] Add lock statistics Adding a new stat kind PGSTAT_KIND_LOCK for the lock statistics. This new statistic kind is a fixed one because its key is the lock type so that we know its size is LOCKTAG_LAST_TYPE + 1. This statistic kind records the following counters: requests waits timeouts deadlock_timeouts deadlocks fastpath No extra details is added (like the ones, i.e relation oid, database oid, we can find in pg_locks). The idea is to provide an idea on what the locking behaviour looks like. XXX: Bump stat file format --- src/backend/storage/lmgr/lock.c | 18 +++ src/backend/storage/lmgr/proc.c | 3 + src/backend/tcop/postgres.c | 8 ++ 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_lock.c | 168 +++++++++++++++++++++++ src/include/pgstat.h | 34 +++++ src/include/utils/pgstat_internal.h | 21 +++ src/include/utils/pgstat_kind.h | 5 +- src/tools/pgindent/typedefs.list | 4 + 11 files changed, 279 insertions(+), 2 deletions(-) 13.3% src/backend/storage/lmgr/ 62.5% src/backend/utils/activity/ 8.3% src/include/utils/ 12.1% src/include/ 3.6% src/ diff --git a/src/backend/storage/lmgr/lock.c b/src/backend/storage/lmgr/lock.c index 62f3471448e..e42919d132b 100644 --- a/src/backend/storage/lmgr/lock.c +++ b/src/backend/storage/lmgr/lock.c @@ -45,6 +45,7 @@ #include "storage/spin.h" #include "storage/standby.h" #include "utils/memutils.h" +#include "utils/pgstat_internal.h" #include "utils/ps_status.h" #include "utils/resowner.h" @@ -870,6 +871,9 @@ LockAcquireExtended(const LOCKTAG *locktag, lockMethodTable->lockModeNames[lockmode]), errhint("Only RowExclusiveLock or less can be acquired on database objects during recovery."))); + /* Increment the lock statistics requests counter */ + pgstat_count_lock_requests(locktag->locktag_type); + #ifdef LOCK_DEBUG if (LOCK_DEBUG_ENABLED(locktag)) elog(LOG, "LockAcquire: lock [%u,%u] %s", @@ -1201,6 +1205,8 @@ LockAcquireExtended(const LOCKTAG *locktag, } else { + /* Increment the lock statistics deadlocks counter */ + pgstat_count_lock_deadlocks(locallock->tag.lock.locktag_type); DeadLockReport(); /* DeadLockReport() will not return */ } @@ -1212,6 +1218,8 @@ LockAcquireExtended(const LOCKTAG *locktag, */ if (waitResult == PROC_WAIT_STATUS_WAITING) { + /* Increment the lock statistics waits counter */ + pgstat_count_lock_waits(locktag->locktag_type); Assert(!dontWait); PROCLOCK_PRINT("LockAcquire: sleeping on lock", proclock); LOCK_PRINT("LockAcquire: sleeping on lock", lock, lockmode); @@ -1232,6 +1240,8 @@ LockAcquireExtended(const LOCKTAG *locktag, * now. */ Assert(!dontWait); + /* Increment the lock statistics deadlocks counter */ + pgstat_count_lock_deadlocks(locallock->tag.lock.locktag_type); DeadLockReport(); /* DeadLockReport() will not return */ } @@ -2767,6 +2777,8 @@ FastPathGrantRelationLock(Oid relid, LOCKMODE lockmode) { Assert(!FAST_PATH_CHECK_LOCKMODE(MyProc, f, lockmode)); FAST_PATH_SET_LOCKMODE(MyProc, f, lockmode); + /* Increment the lock statistics fastpath counter */ + pgstat_count_lock_fastpath(LOCKTAG_RELATION); return true; } } @@ -2776,6 +2788,8 @@ FastPathGrantRelationLock(Oid relid, LOCKMODE lockmode) { MyProc->fpRelId[unused_slot] = relid; FAST_PATH_SET_LOCKMODE(MyProc, unused_slot, lockmode); + /* Increment the lock statistics fastpath counter */ + pgstat_count_lock_fastpath(LOCKTAG_RELATION); ++FastPathLocalUseCounts[group]; return true; } @@ -4600,6 +4614,10 @@ VirtualXactLockTableInsert(VirtualTransactionId vxid) MyProc->fpVXIDLock = true; MyProc->fpLocalTransactionId = vxid.localTransactionId; + /* Increment the lock statistics requests and fastpath counters */ + pgstat_count_lock_requests(LOCKTAG_VIRTUALTRANSACTION); + pgstat_count_lock_fastpath(LOCKTAG_VIRTUALTRANSACTION); + LWLockRelease(&MyProc->fpInfoLock); } diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c index e9ef0fbfe32..dd930fb9bd4 100644 --- a/src/backend/storage/lmgr/proc.c +++ b/src/backend/storage/lmgr/proc.c @@ -1456,9 +1456,12 @@ ProcSleep(LOCALLOCK *locallock) /* check for deadlocks first, as that's probably log-worthy */ if (got_deadlock_timeout) { + /* Increment the lock statistics deadlock_timeouts counter */ + pgstat_count_lock_deadlock_timeouts(locallock->tag.lock.locktag_type); CheckDeadLock(); got_deadlock_timeout = false; } + CHECK_FOR_INTERRUPTS(); } diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c index 0cecd464902..df02719ad63 100644 --- a/src/backend/tcop/postgres.c +++ b/src/backend/tcop/postgres.c @@ -3433,6 +3433,14 @@ ProcessInterrupts(void) if (lock_timeout_occurred) { + LOCALLOCK *lockAwaited; + + lockAwaited = GetAwaitedLock(); + + /* Increment the lock statistics timeouts counter */ + if (lockAwaited) + pgstat_count_lock_timeouts(lockAwaited->tag.lock.locktag_type); + LockErrorCleanup(); ereport(ERROR, (errcode(ERRCODE_LOCK_NOT_AVAILABLE), diff --git a/src/backend/utils/activity/Makefile b/src/backend/utils/activity/Makefile index 9c2443e1ecd..4293d0f9efd 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_lock.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 d8e56b49c24..3d3d7b67b5f 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_lock.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 6bc91ce0dad..d78dc5e3d46 100644 --- a/src/backend/utils/activity/pgstat.c +++ b/src/backend/utils/activity/pgstat.c @@ -83,6 +83,7 @@ * - pgstat_database.c * - pgstat_function.c * - pgstat_io.c + * - pgstat_lock.c * - pgstat_relation.c * - pgstat_replslot.c * - pgstat_slru.c @@ -446,6 +447,23 @@ static const PgStat_KindInfo pgstat_kind_builtin_infos[PGSTAT_KIND_BUILTIN_SIZE] .snapshot_cb = pgstat_io_snapshot_cb, }, + [PGSTAT_KIND_LOCK] = { + .name = "lock", + + .fixed_amount = true, + .write_to_file = true, + + .snapshot_ctl_off = offsetof(PgStat_Snapshot, lock), + .shared_ctl_off = offsetof(PgStat_ShmemControl, lock), + .shared_data_off = offsetof(PgStatShared_Lock, stats), + .shared_data_len = sizeof(((PgStatShared_Lock *) 0)->stats), + + .flush_static_cb = pgstat_lock_flush_cb, + .init_shmem_cb = pgstat_lock_init_shmem_cb, + .reset_all_cb = pgstat_lock_reset_all_cb, + .snapshot_cb = pgstat_lock_snapshot_cb, + }, + [PGSTAT_KIND_SLRU] = { .name = "slru", diff --git a/src/backend/utils/activity/pgstat_lock.c b/src/backend/utils/activity/pgstat_lock.c new file mode 100644 index 00000000000..fb4ed5c7362 --- /dev/null +++ b/src/backend/utils/activity/pgstat_lock.c @@ -0,0 +1,168 @@ +/* ------------------------------------------------------------------------- + * + * pgstat_lock.c + * Implementation of lock statistics. + * + * This file contains the implementation of lock 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) 2021-2025, PostgreSQL Global Development Group + * + * IDENTIFICATION + * src/backend/utils/activity/pgstat_lock.c + * ------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "utils/pgstat_internal.h" + +static PgStat_PendingLock PendingLockStats; +static bool have_lockstats = false; + +/* + * Simpler wrapper of pgstat_lock_flush_cb() + */ +void +pgstat_lock_flush(bool nowait) +{ + (void) pgstat_lock_flush_cb(nowait); +} + +/* + * Flush out locally pending lock statistics + * + * If no stats have been recorded, this function returns false. + * + * If nowait is true, this function returns true if the lock could not be + * acquired. Otherwise, return false. + */ +bool +pgstat_lock_flush_cb(bool nowait) +{ + LWLock *lcktype_lock; + PgStat_LockEntry *lck_shstats; + bool lock_not_acquired = false; + + if (!have_lockstats) + return false; + + for (int i = 0; i <= LOCKTAG_LAST_TYPE; i++) + { + lcktype_lock = &pgStatLocal.shmem->lock.locks[i]; + lck_shstats = + &pgStatLocal.shmem->lock.stats.stats[i]; + + if (!nowait) + LWLockAcquire(lcktype_lock, LW_EXCLUSIVE); + else if (!LWLockConditionalAcquire(lcktype_lock, LW_EXCLUSIVE)) + { + lock_not_acquired = true; + continue; + } + +#define LOCKSTAT_ACC(fld) \ + (lck_shstats->fld += PendingLockStats.stats[i].fld) + LOCKSTAT_ACC(requests); + LOCKSTAT_ACC(waits); + LOCKSTAT_ACC(timeouts); + LOCKSTAT_ACC(deadlock_timeouts); + LOCKSTAT_ACC(deadlocks); + LOCKSTAT_ACC(fastpath); +#undef LOCKSTAT_ACC + + LWLockRelease(lcktype_lock); + } + + memset(&PendingLockStats, 0, sizeof(PendingLockStats)); + + have_lockstats = false; + + return lock_not_acquired; +} + + +void +pgstat_lock_init_shmem_cb(void *stats) +{ + PgStatShared_Lock *stat_shmem = (PgStatShared_Lock *) stats; + + for (int i = 0; i <= LOCKTAG_LAST_TYPE; i++) + LWLockInitialize(&stat_shmem->locks[i], LWTRANCHE_PGSTATS_DATA); +} + +void +pgstat_lock_reset_all_cb(TimestampTz ts) +{ + for (int i = 0; i <= LOCKTAG_LAST_TYPE; i++) + { + LWLock *lcktype_lock = &pgStatLocal.shmem->lock.locks[i]; + PgStat_LockEntry *lck_shstats = &pgStatLocal.shmem->lock.stats.stats[i]; + + LWLockAcquire(lcktype_lock, LW_EXCLUSIVE); + + /* + * Use the lock in the first lock type PgStat_LockEntry to protect the + * reset timestamp as well. + */ + if (i == 0) + pgStatLocal.shmem->lock.stats.stat_reset_timestamp = ts; + + memset(lck_shstats, 0, sizeof(*lck_shstats)); + LWLockRelease(lcktype_lock); + } +} + +void +pgstat_lock_snapshot_cb(void) +{ + for (int i = 0; i <= LOCKTAG_LAST_TYPE; i++) + { + LWLock *lcktype_lock = &pgStatLocal.shmem->lock.locks[i]; + PgStat_LockEntry *lck_shstats = &pgStatLocal.shmem->lock.stats.stats[i]; + PgStat_LockEntry *lck_snap = &pgStatLocal.snapshot.lock.stats[i]; + + LWLockAcquire(lcktype_lock, LW_SHARED); + + /* + * Use the lock in the first lock type PgStat_LockEntry to protect the + * reset timestamp as well. + */ + if (i == 0) + pgStatLocal.snapshot.lock.stat_reset_timestamp = + pgStatLocal.shmem->lock.stats.stat_reset_timestamp; + + /* using struct assignment due to better type safety */ + *lck_snap = *lck_shstats; + LWLockRelease(lcktype_lock); + } +} + +#define PGSTAT_COUNT_LOCK_FUNC(stat) \ +void \ +CppConcat(pgstat_count_lock_,stat)(uint8 locktag_type) \ +{ \ + Assert(locktag_type <= LOCKTAG_LAST_TYPE); \ + PendingLockStats.stats[locktag_type].stat++; \ + have_lockstats = true; \ + pgstat_report_fixed = true; \ +} + +/* pgstat_count_lock_requests */ +PGSTAT_COUNT_LOCK_FUNC(requests) + +/* pgstat_count_lock_waits */ +PGSTAT_COUNT_LOCK_FUNC(waits) + +/* pgstat_count_lock_timeouts */ +PGSTAT_COUNT_LOCK_FUNC(timeouts) + +/* pgstat_count_lock_deadlock_timeouts */ +PGSTAT_COUNT_LOCK_FUNC(deadlock_timeouts) + +/* pgstat_count_lock_deadlocks */ +PGSTAT_COUNT_LOCK_FUNC(deadlocks) + +/* pgstat_count_lock_fastpath */ +PGSTAT_COUNT_LOCK_FUNC(fastpath) diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 202bd2d5ace..7893f7311ae 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -15,6 +15,7 @@ #include "portability/instr_time.h" #include "postmaster/pgarch.h" /* for MAX_XFN_CHARS */ #include "replication/conflict.h" +#include "storage/lock.h" #include "utils/backend_progress.h" /* for backward compatibility */ /* IWYU pragma: export */ #include "utils/backend_status.h" /* for backward compatibility */ /* IWYU pragma: export */ #include "utils/pgstat_kind.h" @@ -339,6 +340,27 @@ typedef struct PgStat_IO PgStat_BktypeIO stats[BACKEND_NUM_TYPES]; } PgStat_IO; +typedef struct PgStat_LockEntry +{ + PgStat_Counter requests; + PgStat_Counter waits; + PgStat_Counter timeouts; + PgStat_Counter deadlock_timeouts; + PgStat_Counter deadlocks; + PgStat_Counter fastpath; +} PgStat_LockEntry; + +typedef struct PgStat_PendingLock +{ + PgStat_LockEntry stats[LOCKTAG_LAST_TYPE + 1]; +} PgStat_PendingLock; + +typedef struct PgStat_Lock +{ + TimestampTz stat_reset_timestamp; + PgStat_LockEntry stats[LOCKTAG_LAST_TYPE + 1]; +} PgStat_Lock; + typedef struct PgStat_StatDBEntry { PgStat_Counter xact_commit; @@ -603,6 +625,18 @@ extern bool pgstat_tracks_io_op(BackendType bktype, IOObject io_object, IOContext io_context, IOOp io_op); +/* + * Functions in pgstat_lock.c + */ + +extern void pgstat_lock_flush(bool nowait); +extern void pgstat_count_lock_requests(uint8 locktag_type); +extern void pgstat_count_lock_waits(uint8 locktag_type); +extern void pgstat_count_lock_timeouts(uint8 locktag_type); +extern void pgstat_count_lock_deadlock_timeouts(uint8 locktag_type); +extern void pgstat_count_lock_deadlocks(uint8 locktag_type); +extern void pgstat_count_lock_fastpath(uint8 locktag_type); + /* * Functions in pgstat_database.c */ diff --git a/src/include/utils/pgstat_internal.h b/src/include/utils/pgstat_internal.h index 6cf00008f63..182feec1235 100644 --- a/src/include/utils/pgstat_internal.h +++ b/src/include/utils/pgstat_internal.h @@ -396,6 +396,16 @@ typedef struct PgStatShared_IO PgStat_IO stats; } PgStatShared_IO; +typedef struct PgStatShared_Lock +{ + /* + * locks[i] protects stats.stats[i]. locks[0] also protects + * stats.stat_reset_timestamp. + */ + LWLock locks[LOCKTAG_LAST_TYPE + 1]; + PgStat_Lock stats; +} PgStatShared_Lock; + typedef struct PgStatShared_SLRU { /* lock protects ->stats */ @@ -492,6 +502,7 @@ typedef struct PgStat_ShmemControl PgStatShared_BgWriter bgwriter; PgStatShared_Checkpointer checkpointer; PgStatShared_IO io; + PgStatShared_Lock lock; PgStatShared_SLRU slru; PgStatShared_Wal wal; @@ -524,6 +535,8 @@ typedef struct PgStat_Snapshot PgStat_IO io; + PgStat_Lock lock; + PgStat_SLRUStats slru[SLRU_NUM_ELEMENTS]; PgStat_WalStats wal; @@ -673,6 +686,14 @@ extern void pgstat_io_init_shmem_cb(void *stats); extern void pgstat_io_reset_all_cb(TimestampTz ts); extern void pgstat_io_snapshot_cb(void); +/* + * Functions in pgstat_lock.c + */ + +extern bool pgstat_lock_flush_cb(bool nowait); +extern void pgstat_lock_init_shmem_cb(void *stats); +extern void pgstat_lock_reset_all_cb(TimestampTz ts); +extern void pgstat_lock_snapshot_cb(void); /* * Functions in pgstat_relation.c diff --git a/src/include/utils/pgstat_kind.h b/src/include/utils/pgstat_kind.h index eb5f0b3ae6d..f8e95f55533 100644 --- a/src/include/utils/pgstat_kind.h +++ b/src/include/utils/pgstat_kind.h @@ -36,8 +36,9 @@ #define PGSTAT_KIND_BGWRITER 8 #define PGSTAT_KIND_CHECKPOINTER 9 #define PGSTAT_KIND_IO 10 -#define PGSTAT_KIND_SLRU 11 -#define PGSTAT_KIND_WAL 12 +#define PGSTAT_KIND_LOCK 11 +#define PGSTAT_KIND_SLRU 12 +#define PGSTAT_KIND_WAL 13 #define PGSTAT_KIND_BUILTIN_MIN PGSTAT_KIND_DATABASE #define PGSTAT_KIND_BUILTIN_MAX PGSTAT_KIND_WAL diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index e6f2e93b2d6..6456d53b13f 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -2218,6 +2218,7 @@ PgStatShared_HashEntry PgStatShared_IO PgStatShared_InjectionPoint PgStatShared_InjectionPointFixed +PgStatShared_Lock PgStatShared_Relation PgStatShared_ReplSlot PgStatShared_SLRU @@ -2240,8 +2241,11 @@ PgStat_HashKey PgStat_IO PgStat_KindInfo PgStat_LocalState +PgStat_Lock +PgStat_LockEntry PgStat_PendingDroppedStatsItem PgStat_PendingIO +PgStat_PendingLock PgStat_SLRUStats PgStat_ShmemControl PgStat_Snapshot -- 2.34.1
>From 6949485ab3951b6a6497f3e312b6d16ec8983210 Mon Sep 17 00:00:00 2001 From: Bertrand Drouvot <bertranddrouvot...@gmail.com> Date: Thu, 31 Jul 2025 09:35:31 +0000 Subject: [PATCH v1 2/2] Add the pg_stat_lock view This new view reports lock statistics. This commit also adds documentation and a few tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 151 ++++++++++++++++++ src/backend/catalog/system_views.sql | 12 ++ src/backend/utils/activity/pgstat_lock.c | 8 + src/backend/utils/adt/pgstatfuncs.c | 39 +++++ src/include/catalog/pg_proc.dat | 9 ++ src/include/pgstat.h | 1 + src/test/isolation/expected/deadlock-hard.out | 20 ++- src/test/isolation/specs/deadlock-hard.spec | 5 +- src/test/regress/expected/advisory_lock.out | 18 +++ src/test/regress/expected/rules.out | 9 ++ src/test/regress/sql/advisory_lock.sql | 4 + 11 files changed, 274 insertions(+), 2 deletions(-) 51.5% doc/src/sgml/ 3.3% src/backend/catalog/ 14.3% src/backend/utils/adt/ 5.7% src/include/catalog/ 8.4% src/test/isolation/expected/ 5.9% src/test/isolation/specs/ 6.7% src/test/regress/expected/ 3.8% src/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 823afe1b30b..0d18124f239 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -493,6 +493,15 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry><structname>pg_stat_lock</structname><indexterm><primary>pg_stat_lock</primary></indexterm></entry> + <entry> + One row for each lock type, containing cluster-wide locks statistics. + See <link linkend="monitoring-pg-stat-lock-view"> + <structname>pg_stat_lock</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication_slots</structname><indexterm><primary>pg_stat_replication_slots</primary></indexterm></entry> <entry>One row per replication slot, showing statistics about the @@ -3027,6 +3036,142 @@ description | Waiting for a newly initialized WAL file to reach durable storage </sect2> + + <sect2 id="monitoring-pg-stat-lock-view"> + <title><structname>pg_stat_lock</structname></title> + + <indexterm> + <primary>pg_stat_lock</primary> + </indexterm> + + <para> + The <structname>pg_stat_lock</structname> view will contain one row for each + lock type, showing cluster-wide locks statistics. + </para> + + <table id="pg-stat-lock-view" xreflabel="pg_stat_lock"> + <title><structname>pg_stat_lock</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"> + <para role="column_definition"> + Column Type + </para> + <para> + Description + </para> + </entry> + </row> + </thead> + <tbody> + <row> + <entry role="catalog_table_entry"> + <para role="column_definition"> + <structfield>locktype</structfield> <type>text</type> + </para> + <para> + Type of the lockable object: + <literal>relation</literal>, + <literal>extend</literal>, + <literal>frozenid</literal>, + <literal>page</literal>, + <literal>tuple</literal>, + <literal>transactionid</literal>, + <literal>virtualxid</literal>, + <literal>spectoken</literal>, + <literal>object</literal>, + <literal>userlock</literal>, + <literal>advisory</literal>, or + <literal>applytransaction</literal>. + (See also <xref linkend="wait-event-lock-table"/>.) + </para> + </entry> + </row> + + <row> + <entry role="catalog_table_entry"> + <para role="column_definition"> + <structfield>requests</structfield> <type>bigint</type> + </para> + <para> + Number of requests for this lock type. + </para> + </entry> + </row> + + <row> + <entry role="catalog_table_entry"> + <para role="column_definition"> + <structfield>waits</structfield> <type>bigint</type> + </para> + <para> + Number of times requests for this lock type had to wait. + </para> + </entry> + </row> + + <row> + <entry role="catalog_table_entry"> + <para role="column_definition"> + <structfield>timeouts</structfield> <type>bigint</type> + </para> + <para> + Number of times requests for this lock type had to wait longer + than <varname>lock_timeout</varname>. + </para> + </entry> + </row> + + <row> + <entry role="catalog_table_entry"> + <para role="column_definition"> + <structfield>deadlock_timeouts</structfield> <type>bigint</type> + </para> + <para> + Number of times requests for this lock type had to wait longer + than <varname>deadlock_timeout</varname>. + </para> + </entry> + </row> + + <row> + <entry role="catalog_table_entry"> + <para role="column_definition"> + <structfield>deadlocks</structfield> <type>bigint</type> + </para> + <para> + Number of times a deadlock occurred on this lock type. + </para> + </entry> + </row> + + <row> + <entry role="catalog_table_entry"> + <para role="column_definition"> + <structfield>fastpath</structfield> <type>bigint</type> + </para> + <para> + Number of times this lock type was taken via fast path. + </para> + </entry> + </row> + + <row> + <entry role="catalog_table_entry"> + <para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset. + </para> + </entry> + </row> + </tbody> + </tgroup> + </table> + </sect2> + <sect2 id="monitoring-pg-stat-bgwriter-view"> <title><structname>pg_stat_bgwriter</structname></title> @@ -5044,6 +5189,12 @@ description | Waiting for a newly initialized WAL file to reach durable storage <structname>pg_stat_io</structname> view. </para> </listitem> + <listitem> + <para> + <literal>lock</literal>: Reset all the counters shown in the + <structname>pg_stat_lock</structname> view. + </para> + </listitem> <listitem> <para> <literal>recovery_prefetch</literal>: Reset all the counters shown in diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index f6eca09ee15..d493a00c544 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -950,6 +950,18 @@ CREATE VIEW pg_stat_slru AS s.stats_reset FROM pg_stat_get_slru() s; +CREATE VIEW pg_stat_lock AS + SELECT + l.locktype, + l.requests, + l.waits, + l.timeouts, + l.deadlock_timeouts, + l.deadlocks, + l.fastpath, + l.stats_reset + FROM pg_stat_get_lock() l; + CREATE VIEW pg_stat_wal_receiver AS SELECT s.pid, diff --git a/src/backend/utils/activity/pgstat_lock.c b/src/backend/utils/activity/pgstat_lock.c index fb4ed5c7362..73bdeb5ff66 100644 --- a/src/backend/utils/activity/pgstat_lock.c +++ b/src/backend/utils/activity/pgstat_lock.c @@ -21,6 +21,14 @@ static PgStat_PendingLock PendingLockStats; static bool have_lockstats = false; +PgStat_Lock * +pgstat_fetch_stat_lock(void) +{ + pgstat_snapshot_fixed(PGSTAT_KIND_LOCK); + + return &pgStatLocal.snapshot.lock; +} + /* * Simpler wrapper of pgstat_lock_flush_cb() */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1c12ddbae49..5e63f2d2a38 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -1696,6 +1696,42 @@ pg_stat_get_wal(PG_FUNCTION_ARGS) wal_stats->stat_reset_timestamp)); } +Datum +pg_stat_get_lock(PG_FUNCTION_ARGS) +{ +#define PG_STAT_LOCK_COLS 8 + ReturnSetInfo *rsinfo; + PgStat_Lock *lock_stats; + + InitMaterializedSRF(fcinfo, 0); + rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + lock_stats = pgstat_fetch_stat_lock(); + + for (int lcktype = 0; lcktype <= LOCKTAG_LAST_TYPE; lcktype++) + { + const char *locktypename; + Datum values[PG_STAT_LOCK_COLS] = {0}; + bool nulls[PG_STAT_LOCK_COLS] = {0}; + PgStat_LockEntry *lck_stats = &lock_stats->stats[lcktype]; + + locktypename = LockTagTypeNames[lcktype]; + + values[0] = CStringGetTextDatum(locktypename); + values[1] = Int64GetDatum(lck_stats->requests); + values[2] = Int64GetDatum(lck_stats->waits); + values[3] = Int64GetDatum(lck_stats->timeouts); + values[4] = Int64GetDatum(lck_stats->deadlock_timeouts); + values[5] = Int64GetDatum(lck_stats->deadlocks); + values[6] = Int64GetDatum(lck_stats->fastpath); + values[7] = TimestampTzGetDatum(lock_stats->stat_reset_timestamp); + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + } + + return (Datum) 0; +} + /* * Returns statistics of SLRU caches. */ @@ -1880,6 +1916,7 @@ pg_stat_reset_shared(PG_FUNCTION_ARGS) pgstat_reset_of_kind(PGSTAT_KIND_BGWRITER); pgstat_reset_of_kind(PGSTAT_KIND_CHECKPOINTER); pgstat_reset_of_kind(PGSTAT_KIND_IO); + pgstat_reset_of_kind(PGSTAT_KIND_LOCK); XLogPrefetchResetStats(); pgstat_reset_of_kind(PGSTAT_KIND_SLRU); pgstat_reset_of_kind(PGSTAT_KIND_WAL); @@ -1897,6 +1934,8 @@ pg_stat_reset_shared(PG_FUNCTION_ARGS) pgstat_reset_of_kind(PGSTAT_KIND_CHECKPOINTER); else if (strcmp(target, "io") == 0) pgstat_reset_of_kind(PGSTAT_KIND_IO); + else if (strcmp(target, "lock") == 0) + pgstat_reset_of_kind(PGSTAT_KIND_LOCK); else if (strcmp(target, "recovery_prefetch") == 0) XLogPrefetchResetStats(); else if (strcmp(target, "slru") == 0) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 3ee8fed7e53..c5faff30317 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -6001,6 +6001,15 @@ proargnames => '{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_io' }, +{ oid => '9375', descr => 'statistics: per lock type statistics', + proname => 'pg_stat_get_lock', prorows => '10', proretset => 't', + provolatile => 'v', proparallel => 'r', prorettype => 'record', + proargtypes => '', + proallargtypes => '{text,int8,int8,int8,int8,int8,int8,timestamptz}', + proargmodes => '{o,o,o,o,o,o,o,o}', + proargnames => '{locktype,requests,waits,timeouts,deadlock_timeouts,deadlocks,fastpath,stats_reset}', + prosrc => 'pg_stat_get_lock' }, + { oid => '6386', descr => 'statistics: backend IO statistics', proname => 'pg_stat_get_backend_io', prorows => '5', proretset => 't', provolatile => 'v', proparallel => 'r', prorettype => 'record', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 7893f7311ae..e653bce4aab 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -636,6 +636,7 @@ extern void pgstat_count_lock_timeouts(uint8 locktag_type); extern void pgstat_count_lock_deadlock_timeouts(uint8 locktag_type); extern void pgstat_count_lock_deadlocks(uint8 locktag_type); extern void pgstat_count_lock_fastpath(uint8 locktag_type); +extern PgStat_Lock *pgstat_fetch_stat_lock(void); /* * Functions in pgstat_database.c diff --git a/src/test/isolation/expected/deadlock-hard.out b/src/test/isolation/expected/deadlock-hard.out index 460653f2b86..ff448da5299 100644 --- a/src/test/isolation/expected/deadlock-hard.out +++ b/src/test/isolation/expected/deadlock-hard.out @@ -1,6 +1,12 @@ Parsed test spec with 8 sessions -starting permutation: s1a1 s2a2 s3a3 s4a4 s5a5 s6a6 s7a7 s8a8 s1a2 s2a3 s3a4 s4a5 s5a6 s6a7 s7a8 s8a1 s8c s7c s6c s5c s4c s3c s2c s1c +starting permutation: s1rl s1a1 s2a2 s3a3 s4a4 s5a5 s6a6 s7a7 s8a8 s1a2 s2a3 s3a4 s4a5 s5a6 s6a7 s7a8 s8a1 s8c s8f s7c s6c s5c s4c s3c s2c s1c s1sl +step s1rl: SELECT pg_stat_reset_shared('lock'); +pg_stat_reset_shared +-------------------- + +(1 row) + step s1a1: LOCK TABLE a1; step s2a2: LOCK TABLE a2; step s3a3: LOCK TABLE a3; @@ -21,6 +27,12 @@ step s8a1: <... completed> ERROR: deadlock detected step s7a8: <... completed> step s8c: COMMIT; +step s8f: SELECT pg_stat_force_next_flush(); +pg_stat_force_next_flush +------------------------ + +(1 row) + step s7c: COMMIT; step s6a7: <... completed> step s6c: COMMIT; @@ -34,3 +46,9 @@ step s2a3: <... completed> step s2c: COMMIT; step s1a2: <... completed> step s1c: COMMIT; +step s1sl: SELECT deadlocks > 0, deadlock_timeouts > 0 FROM pg_stat_lock WHERE locktype = 'relation'; +?column?|?column? +--------+-------- +t |t +(1 row) + diff --git a/src/test/isolation/specs/deadlock-hard.spec b/src/test/isolation/specs/deadlock-hard.spec index 60bedca237a..6e8330662b4 100644 --- a/src/test/isolation/specs/deadlock-hard.spec +++ b/src/test/isolation/specs/deadlock-hard.spec @@ -25,6 +25,8 @@ setup { BEGIN; SET deadlock_timeout = '100s'; } step s1a1 { LOCK TABLE a1; } step s1a2 { LOCK TABLE a2; } step s1c { COMMIT; } +step s1sl { SELECT deadlocks > 0, deadlock_timeouts > 0 FROM pg_stat_lock WHERE locktype = 'relation'; } +step s1rl { SELECT pg_stat_reset_shared('lock'); } session s2 setup { BEGIN; SET deadlock_timeout = '100s'; } @@ -67,6 +69,7 @@ setup { BEGIN; SET deadlock_timeout = '10ms'; } step s8a8 { LOCK TABLE a8; } step s8a1 { LOCK TABLE a1; } step s8c { COMMIT; } +step s8f { SELECT pg_stat_force_next_flush(); } # Note: when s8a1 detects the deadlock and fails, s7a8 is released, making # it timing-dependent which query completion is received first by the tester. @@ -76,4 +79,4 @@ step s8c { COMMIT; } # dummy blocking mark to s8a1 to ensure it will be reported as "waiting" # regardless of that. -permutation s1a1 s2a2 s3a3 s4a4 s5a5 s6a6 s7a7 s8a8 s1a2 s2a3 s3a4 s4a5 s5a6 s6a7 s7a8(s8a1) s8a1(*) s8c s7c s6c s5c s4c s3c s2c s1c +permutation s1rl s1a1 s2a2 s3a3 s4a4 s5a5 s6a6 s7a7 s8a8 s1a2 s2a3 s3a4 s4a5 s5a6 s6a7 s7a8(s8a1) s8a1(*) s8c s8f s7c s6c s5c s4c s3c s2c s1c s1sl diff --git a/src/test/regress/expected/advisory_lock.out b/src/test/regress/expected/advisory_lock.out index 02e07765ac2..fdaa1756ba4 100644 --- a/src/test/regress/expected/advisory_lock.out +++ b/src/test/regress/expected/advisory_lock.out @@ -2,6 +2,12 @@ -- ADVISORY LOCKS -- SELECT oid AS datoid FROM pg_database WHERE datname = current_database() \gset +SELECT pg_stat_reset_shared('lock'); + pg_stat_reset_shared +---------------------- + +(1 row) + BEGIN; SELECT pg_advisory_xact_lock(1), pg_advisory_xact_lock_shared(2), @@ -48,6 +54,12 @@ WARNING: you don't own a lock of type ShareLock f | f | f | f (1 row) +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + -- automatically release xact locks at commit COMMIT; SELECT count(*) FROM pg_locks WHERE locktype = 'advisory' AND database = :datoid; @@ -56,6 +68,12 @@ SELECT count(*) FROM pg_locks WHERE locktype = 'advisory' AND database = :datoid 0 (1 row) +SELECT requests FROM pg_stat_lock WHERE locktype = 'advisory'; + requests +---------- + 4 +(1 row) + BEGIN; -- holding both session and xact locks on the same objects, xact first SELECT diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index dce8c672b40..c790164ea82 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1938,6 +1938,15 @@ 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_lock| SELECT locktype, + requests, + waits, + timeouts, + deadlock_timeouts, + deadlocks, + fastpath, + stats_reset + FROM pg_stat_get_lock() l(locktype, requests, waits, timeouts, deadlock_timeouts, deadlocks, fastpath, stats_reset); pg_stat_progress_analyze| SELECT s.pid, s.datid, d.datname, diff --git a/src/test/regress/sql/advisory_lock.sql b/src/test/regress/sql/advisory_lock.sql index 8513ab8e98f..f1bff60fd37 100644 --- a/src/test/regress/sql/advisory_lock.sql +++ b/src/test/regress/sql/advisory_lock.sql @@ -4,6 +4,8 @@ SELECT oid AS datoid FROM pg_database WHERE datname = current_database() \gset +SELECT pg_stat_reset_shared('lock'); + BEGIN; SELECT @@ -26,12 +28,14 @@ SELECT pg_advisory_unlock(1), pg_advisory_unlock_shared(2), pg_advisory_unlock(1, 1), pg_advisory_unlock_shared(2, 2); +SELECT pg_stat_force_next_flush(); -- automatically release xact locks at commit COMMIT; SELECT count(*) FROM pg_locks WHERE locktype = 'advisory' AND database = :datoid; +SELECT requests FROM pg_stat_lock WHERE locktype = 'advisory'; BEGIN; -- 2.34.1