PostgreSQL 17
Wait events that arrive with their own glossary
For anyone who has met an unfamiliar wait event name on a dashboard.
Reference page, revised in place. Last updated .
Names the server used and never defined anywhere you were looking
A session that is not running is waiting for something, and the activity view has reported what for a long time, as a pair of short strings: a type such as LWLock or IO, and a name such as BufferMapping or WalSync. Those names are precise and they are opaque. There is a table in the manual that defines each one, and it lives several chapters away from wherever the name first appeared in front of you, under a heading nobody remembers.
That created a small and constant tax. A name on a dashboard sends somebody to a browser. Worse, the set of names changes between majors: events are added, a few are renamed, and a dashboard that hardcodes a list of “interesting” waits silently stops covering the ones introduced after it was written. Nothing tells you that, because an event that exists and is never matched looks exactly like an event that never happens.
PostgreSQL 17 published the list as a view. pg_wait_events has one row per wait event the running server knows about, carrying its type, its name and a sentence describing it. It is recorded under Monitoring in the PostgreSQL 17 release notes. This is a pure addition, so there is no before to show on 16 beyond the absence of the view itself.
What a 16 server says when asked
SELECT type, name, description FROM pg_wait_events WHERE type = 'LWLock' LIMIT 3;
ERROR: relation "pg_wait_events" does not exist
LINE 1: SELECT type, name, description FROM pg_wait_events WHERE typ...
^
The error is a missing relation rather than a missing column, which matters for anyone maintaining one query across a mixed fleet: this one cannot be salvaged with a coalesce or an alias. It is present or it is not.
On 17, the shape of the catalog is worth seeing before the join, because the distribution is the first surprise.
SELECT type, count(*) AS events
FROM pg_wait_events
GROUP BY type
ORDER BY events DESC, type;
type | events
-----------+--------
LWLock | 81
IO | 77
IPC | 58
Activity | 16
Lock | 12
Timeout | 10
Client | 9
BufferPin | 1
Extension | 1
(9 rows)
Lightweight locks and I/O dominate, which is the opposite of where most dashboards put their attention. Lock is the type that covers the heavyweight locks people usually mean when they say a query is blocked, and it is one of the smaller groups.
A single lookup reads the way a glossary should.
SELECT type, name, description
FROM pg_wait_events
WHERE name IN ('WalSync', 'BufferMapping', 'CheckpointWriteDelay')
ORDER BY type, name;
type | name | description
---------+----------------------+--------------------------------------------------------------------
IO | WalSync | Waiting for a WAL file to reach durable storage
LWLock | BufferMapping | Waiting to associate a data block with a buffer in the buffer pool
Timeout | CheckpointWriteDelay | Waiting between writes while performing a checkpoint
(3 rows)
Making the activity view explain itself
The join is the point of the view. Two columns in the activity view, two columns in the catalog, and a description arrives with every waiting session.
SELECT a.backend_type,
a.wait_event_type,
a.wait_event,
w.description
FROM pg_stat_activity a
JOIN pg_wait_events w
ON w.type = a.wait_event_type AND w.name = a.wait_event
WHERE a.backend_type <> 'client backend'
ORDER BY a.wait_event_type, a.wait_event;
backend_type | wait_event_type | wait_event | description
------------------------------+-----------------+---------------------+--------------------------------------------------------------
autovacuum launcher | Activity | AutovacuumMain | Waiting in main loop of autovacuum launcher process
background writer | Activity | BgwriterMain | Waiting in main loop of background writer process
checkpointer | Activity | CheckpointerMain | Waiting in main loop of checkpointer process
logical replication launcher | Activity | LogicalLauncherMain | Waiting in main loop of logical replication launcher process
walwriter | Activity | WalWriterMain | Waiting in main loop of WAL writer process
(5 rows)
Those rows are the server’s own background processes sitting idle, which is what a quiet cluster looks like from the inside. The same join on a busy cluster, restricted to client backends, is the query worth putting behind an on-call dashboard, because the answer arrives already explained.
Use an inner join deliberately, and know what it discards. A session that is not waiting has nulls in both columns and drops out, which is usually what you want. A session waiting on an extension’s own event appears in the activity view under the Extension type, and whether the catalog can describe it depends on the extension, so a left join is the safer default if your fleet loads much beyond the standard set.
The dashboard change, and why there is no alert here
Nothing in this view is a number, so nothing in it is a threshold. What it changes is the quality of the thing a human reads when an alert fires somewhere else.
Two uses are worth the effort. The first is the join above, on the panel that lists what is currently waiting, so that a name nobody recognises arrives with its definition attached. The second is a coverage check at upgrade time: compare the set of names the server knows against the set your rules mention, and the difference is the list of waits you are not watching. On 16 that comparison had to be made against a copy of the documentation; on 17 it is a query against the server that will actually be running.
Counting rows in this view across majors is also the cheapest way to see how much the wait vocabulary has grown, which is a useful argument when somebody proposes that a dashboard written three majors ago is still complete.
What it costs to read
The view is generated from a static table compiled into the server, so it does not read shared memory, does not take a snapshot and does not depend on any setting. There is no track_* to enable and no restart. It is as cheap as a catalog lookup and it does not change while the server runs.
The activity view it joins to is the usual cost: a row per backend assembled at query time, plus the visibility rules that hide other users’ query text unless you have the right membership. Neither of those changed in 17, and the join adds nothing measurable to either.