List PostgreSQL Wait Events with the pg_wait_events View

List PostgreSQL Wait Events with the pg_wait_events View

PostgreSQL 17 added pg_wait_events, a system view that enumerates every wait event the running server can record. Prior to 17 the same information lived only in the documentation — no in-database way to confirm a wait name or attach a description to it. The view is read-only, three columns wide, and exactly the lookup table a monitoring dashboard wants alongside pg_stat_activity.

Purpose and Overview

Wait-event monitoring is one of the highest-signal PostgreSQL observability surfaces. When a backend is not actively running CPU work, PostgreSQL records why it is paused in two columns of pg_stat_activity: wait_event_type (the broad category) and wait_event (the specific reason). Tracking which waits dominate over time tells a DBA whether a workload is bound by lock contention, I/O latency, internal lightweight-lock contention, or client-side stalls — a sharper diagnostic frame than CPU utilization or query duration alone.

pg_wait_events complements pg_stat_activity rather than replacing it. Where pg_stat_activity answers which session is waiting right now, pg_wait_events answers what this wait actually means. The view returns every wait the running server can post — categorized by type, named in the same string the activity view uses, and described in one human-readable sentence per row. The descriptions are maintained alongside the source code, which means they evolve as new wait events are instrumented and old ones are renamed.

The view is read-only and accessible to every role. There are no privilege grants to manage, no statistics to reset, and no runtime cost to query. Treat it as documentation that ships with the server — a version-pinned manifest of monitorable conditions for the running binary.

Sample Code

1-- Enumerate every wait event the server can record (PostgreSQL 17+)
2SELECT
3    type,
4    name,
5    description
6FROM pg_wait_events
7ORDER BY type, name;

Notes: Requires PostgreSQL 17 or later. The view is part of the base catalog and readable by any role. Returns roughly 350 rows on a stock 17.x server; the count grows with each release as new internal waits are exposed.

Code Breakdown

The query is intentionally minimal: a flat SELECT against a three-column catalog with a stable ordering. Each piece is worth understanding because the choices shape how the result is consumed downstream.

The pg_wait_events View

pg_wait_events is a system view introduced in PostgreSQL 17. It sits in the pg_catalog schema and is implicitly searchable from any role. One row exists per defined wait event in the running server binary; on a stock 17.x installation that is roughly 350 rows, with the count growing each major release. The view is a static catalog — the rows do not change between queries on the same server version, so the same result will return millisecond after millisecond.

Columns Returned

Three columns are exposed. type (text) is the broad wait event category — LWLock, Lock, IO, IPC, Client, Timeout, BufferPin, Extension, or Activity. name (text) is the specific wait inside the category, matching exactly the string pg_stat_activity.wait_event uses when a backend is in that wait. description (text) is the one-line human-readable explanation maintained alongside the C source code.

Ordering and Reading Strategy

ORDER BY type, name groups the result by category, which is the natural way to read the catalog because categories map directly to the diagnostic frames a DBA reaches for. Reading all IO rows in one block surfaces the disk-related waits as a coherent set; reading all Lock rows surfaces the relational-lock surface. The alphabetical secondary sort within each category matches the order the PostgreSQL documentation uses.

Key Wait Event Categories

Lock and LWLock

Lock waits represent heavyweight locks held between transactions — the locking layer SQL programmers reason about directly via LOCK TABLE, SELECT ... FOR UPDATE, and implicit row-level locks from UPDATE/DELETE. The specific wait names (relation, tuple, transactionid, virtualxid) point to the lock granularity. LWLock waits are internal lightweight locks PostgreSQL uses to coordinate access to shared-memory data structures. Names like BufferMapping, LockManager, and WALWrite identify the contention point inside the server.

IO

IO waits cover disk reads and writes — DataFileRead for a heap or index page fetch, WALWrite and WALSync for WAL flushing, DataFileExtend for table growth, and RelationMapRead for the relation-mapping bootstrap. A workload dominated by IO waits is usually addressed by increasing shared_buffers, improving disk throughput, or both — though WALSync specifically points at the WAL volume's commit latency rather than data-file throughput.

IPC and Client

IPC waits cover inter-process communication: backends waiting on the WAL writer to flush, on the checkpointer to finish, on a parallel worker to return results, or on a logical replication apply worker. Client waits cover the application side — ClientRead (waiting for the next query) is the most common and usually points to a connection-pool or application-side issue, not a server problem.

Timeout, BufferPin, and Activity

Timeout waits are explicit sleeps — PgSleep, RecoveryRetrieveRetryInterval, VacuumDelay. BufferPin is a short-term wait while a buffer is pinned by another backend; persistent BufferPin waits are rare. Activity waits are the long-idle states of background workers — AutovacuumLauncherMain, CheckpointerMain, WalWriterMain, LogicalLauncherMain. These are normal, not contention; suppress them on operational dashboards.

Extension

Extension waits are defined by loaded extensions. Common examples: pg_stat_statements waits, logical replication output plugin waits, partition routing waits from third-party extensions. These rows only appear in pg_wait_events when the extension is loaded into the server, so the catalog reflects what the running configuration can actually report.

Practical Applications

The view's value compounds when joined to live activity or compared across versions.

Live-Waits Dashboard Lookup

The canonical use case is joining pg_stat_activity to pg_wait_events so a dashboard renders both the wait name and its description without hard-coding a lookup table. This keeps the dashboard accurate as the catalog evolves between minor releases.

 1SELECT
 2    sa.pid,
 3    sa.datname,
 4    sa.usename,
 5    sa.wait_event_type,
 6    sa.wait_event,
 7    we.description,
 8    LEFT(sa.query, 100)  AS query_snippet
 9FROM pg_stat_activity sa
10LEFT JOIN pg_wait_events we
11       ON we.type = sa.wait_event_type
12      AND we.name = sa.wait_event
13WHERE sa.wait_event IS NOT NULL
14ORDER BY sa.wait_event_type, sa.wait_event;

Activity-Class Suppression for Operational Monitoring

The Activity class floods live-wait dashboards with idle background-worker waits — interesting once to confirm those processes are alive, useless for the rest of the day. Excluding the category removes the noise without losing visibility into the production-relevant IO, Lock, and LWLock waits that signal actual work being held up.

 1SELECT
 2    sa.pid,
 3    sa.wait_event_type,
 4    sa.wait_event,
 5    we.description,
 6    LEFT(sa.query, 80)   AS query_snippet
 7FROM pg_stat_activity sa
 8LEFT JOIN pg_wait_events we
 9       ON we.type = sa.wait_event_type
10      AND we.name = sa.wait_event
11WHERE sa.wait_event IS NOT NULL
12  AND sa.wait_event_type <> 'Activity';

Cross-Version Upgrade Diff

Capturing the catalog on the old cluster, capturing again on the new one, and EXCEPT-diffing the two surfaces every wait the upgrade added. PostgreSQL 17 introduced several new entries as wait-event instrumentation was extended; later major versions add more as new I/O paths land. The diff turns a sometimes-vague release note into a concrete list of new monitoring points to surface in dashboards.

1SELECT type, name FROM pg_wait_events
2EXCEPT
3SELECT type, name FROM old_cluster_wait_events_snapshot
4ORDER BY type, name;

Extension Wait Event Debugging

When a loaded extension introduces new waits, those rows appear in pg_wait_events under type = 'Extension'. Querying the catalog filtered to WHERE type = 'Extension' after installing or upgrading an extension confirms which extension-defined waits the server can now report — which makes troubleshooting that extension's idle states tractable instead of guesswork.

Version Compatibility

pg_wait_events was added in PostgreSQL 17 and is not available on earlier major versions. On PostgreSQL 16 and earlier the same information is documented in the manual under "Wait Event Types" but has no in-database surface — the workaround is to scrape the documentation HTML into a static lookup table and join it against pg_stat_activity until you can upgrade. Plan to retire that static table once the cluster moves to 17 or later.

The source-of-truth file that powers pg_wait_events is src/backend/utils/activity/wait_event_names.txt in the PostgreSQL source repository. The build process generates the C enum, the documentation tables, and the runtime rows from that single file. Reading the file in your installed major version's branch shows exactly which waits exist and how they are described — useful when a description in the running view seems too terse to interpret on its own.

PostgreSQL 18 adds further wait events as asynchronous I/O work landed, so a 17-to-18 upgrade is one of the cases where the EXCEPT diff in the practical applications section yields the most material change.

Best Practices

  • Treat the view as documentation that ships with the server — version-pinned, doesn't change at runtime, no statistics to reset
  • Join live wait activity rather than hard-coding a lookup — the joined query stays accurate across minor releases without code changes
  • Suppress the Activity class on operational dashboards — long-idle background-worker waits are normal and drown out the real signal
  • Snapshot the catalog before every major upgradeEXCEPT against the new-version catalog reveals every wait the upgrade instrumented
  • Read wait_event_names.txt for the underlying definitions — when a one-line description doesn't disambiguate, the source file's tab-separated rows include compiler-level context
  • No privilege grants neededpg_monitor (PostgreSQL 10+) already covers pg_stat_activity; pg_wait_events is world-readable on top of that

References

Posts in this series