Skip to main content
RunBook Academy

PostgreSQLXVI · Observability, Logging and AlertingObservability

Wait events: what is this backend actually waiting on?

Advanced⏱ ~30 minpsql

What you'll learn

  • Read wait_event_type and wait_event and know what each class means
  • Sample wait events to build a profile rather than a snapshot
  • Distinguish contention from I/O from genuine work
  • Corroborate a diagnosis with a second instrument

Prerequisites

Verified against PostgreSQL 18.x · PostgreSQL (comparison targets) 17.11, 16.15 · PostgreSQL (support calendar) 18, 17, 16, 15, 14 supported · pgBackRest 2.59.1 · PgBouncer 1.25.2 · Patroni 4.1.5 · Ubuntu (host baseline) 26.04 LTS · 2026-08-27

Not yet marked complete on this device.

state = 'active' means a backend is executing a statement. It does not mean the backend is doing anything. The wait event tells you which.

The classes

pg_stat_activity.wait_event_type puts every wait into a class, and the class alone narrows the problem enormously.

TypeMeansTypically
LockWaiting for a heavyweight lockApplication contention. Part IX
LWLockWaiting for an internal shared-memory lockInternal contention
IOWaiting for storageSlow disk, or too little cache
BufferPinWaiting to pin a buffer another backend holdsRare
ClientWaiting for the clientNot the database’s problem
IPCWaiting for another PostgreSQL processParallel query, sync rep
TimeoutSleeping deliberatelyUsually normal
ExtensionAn extension’s own waitDepends
NULLNot waiting. Actually runningOn CPU

Sampling, not snapshotting

A single query against pg_stat_activity catches whatever happened to be true at that instant. One sample is an anecdote. A profile needs many.

Read-only / Safewait events sampled six times during a 16-client run
$ SELECT wait_event_type, wait_event, count(*)
FROM pg_stat_activity
WHERE backend_type='client backend' AND state='active'
GROUP BY 1,2 ORDER BY 3 DESC;
      6 IO|WalSync|1
    5 (running)|-|1
    3 Lock|tuple|3
    3 LWLock|WALWrite|2
    2 Lock|tuple|2
    2 Lock|transactionid|10
    2 LWLock|WALWrite|3
    1 Lock|transactionid|11
    1 Lock|transactionid|9
    1 Lock|transactionid|8
    1 Lock|transactionid|6

Up to eleven of sixteen backends waiting on Lock/transactionid simultaneously.

Lock/transactionid means: this backend wants a row that another transaction has modified and not yet committed, so it is waiting for that transaction to finish. It is row-level contention, and it is exactly Part IX’s subject seen from the waiting side.

The rest of the profile is consistent: Lock/tuple is the queue for the same rows, LWLock/WALWrite and IO/WalSync are the WAL write path under load.

-- a poor man's sampler: run every second, accumulate, and look at the shape
SELECT now(), wait_event_type, wait_event, count(*)
  FROM pg_stat_activity
 WHERE backend_type = 'client backend' AND state = 'active'
 GROUP BY 1, 2, 3;

Reading the common ones

EventMeansLook at
Lock/transactionidWaiting for another transaction’s rowThe blocking transaction. Lesson IX-03
Lock/relationWaiting for a table lockDDL, or LOCK TABLE. Lesson IX-05
Lock/tupleQueued for a row lockSame root cause
LWLock/WALWriteWaiting to write WALCommit rate, wal_buffers
LWLock/BufferContentContention on one bufferA very hot page
IO/DataFileReadReading a table or indexshared_buffers, storage
IO/WalSyncWaiting for WAL fsyncStorage commit latency. Lesson VI-01
Client/ClientReadWaiting for the applicationThe application
IPC/SyncRepWaiting for a synchronous standbyLesson XIV-05
Timeout/VacuumDelayAutovacuum’s cost delayLesson VIII-03

What to take from this

  • state = 'active' says executing, not working. The wait event says which.
  • The class narrows it enormously: Lock is application contention, IO is storage, Client is not your problem.
  • Client/ClientRead with idle in transaction is lesson VII-05’s problem in one row.
  • Sample repeatedly; one reading is an anecdote.
  • Measured: 11 of 16 backends on Lock/transactionid, corroborating pg_stat_statements from the opposite direction.
  • PostgreSQL keeps no wait history by design. Sample for shape, use log_lock_waits for individual events.

Cross-course references

  • Linux for Production Sysadmins — Part XXXVIII (Linux performance fundamentals) and Part XLIII (eBPF) cover confirming a wait event against what the kernel says the process is doing.
  • Observability for Production Sysadmins — Part LIX (Database observability) covers sampling wait events into a series, since a single sample is an anecdote.

Quiz

Knowledge check · 6 questions

  1. Q1. A backend shows state 'idle in transaction' with wait_event Client/ClientRead. What is happening?

  2. Q2. Sampling wait events at 1 Hz on a system whose average wait is 5 ms. What is that good for and not good for?

  3. Q3. Why does PostgreSQL keep no history of wait events?

  4. Q4. Which wait events point at application-level lock contention? Select all that apply.

  5. Q5. A backend with wait_event NULL and state active is running on CPU rather than waiting.

  6. Q6. The wait-event profile showed eleven of sixteen backends on Lock/transactionid. How would you corroborate that diagnosis, and why bother?

Passing score: 75%. Answers are checked in this browser.