The essentials

Quick reference

One focused task per row. Jump to the related section for complete, working examples.

UseSyntaxExamples
List active sessionsSELECT pid, usename, application_name, state, query_start FROM pg_stat_activity WHERE state = 'active';View examples
Measure transaction ageclock_timestamp() - xact_start AS transaction_ageView examples
Find idle transactionsWHERE state = 'idle in transaction' ORDER BY xact_start NULLS LASTView examples
Inspect wait eventsSELECT pid, wait_event_type, wait_event FROM pg_stat_activity WHERE wait_event IS NOT NULL;View examples
Find direct blockersSELECT pid, pg_blocking_pids(pid) AS blocking_pids FROM pg_stat_activity WHERE cardinality(pg_blocking_pids(pid)) > 0;View examples
Bound lock waitingSET LOCAL lock_timeout = '2s';View examples
Rank total execution timeSELECT queryid, calls, total_exec_time FROM pg_stat_statements ORDER BY total_exec_time DESC LIMIT 20;View examples
Rank mean execution timeSELECT queryid, calls, mean_exec_time FROM pg_stat_statements WHERE calls >= 100 ORDER BY mean_exec_time DESC;View examples
Calculate cache hit ratioblks_hit::numeric / NULLIF(blks_hit + blks_read, 0)View examples
Inspect backend I/OSELECT backend_type, object, context, reads, read_time, writes, write_time FROM pg_stat_io;View examples
Monitor VACUUM progressSELECT pid, relid::regclass, phase, heap_blks_scanned, heap_blks_total FROM pg_stat_progress_vacuum;View examples
Monitor index creationSELECT pid, relid::regclass, phase, blocks_done, blocks_total FROM pg_stat_progress_create_index;View examples
Measure replay byte lagpg_wal_lsn_diff(pg_current_wal_lsn(), replay_lsn) AS replay_byte_lagView examples
Inspect replication slotsSELECT slot_name, slot_type, active, restart_lsn, wal_status FROM pg_replication_slots;View examples
Refresh cached statisticsSELECT pg_stat_clear_snapshot();View examples

PostgreSQL exposes live session state, cumulative counters, wait events, progress views, and optional normalized statement statistics. Useful monitoring combines them with timestamps and rate calculations rather than interpreting one snapshot in isolation. Collect the least-sensitive fields needed, establish workload baselines, and investigate before terminating sessions or resetting statistics.

Step by step

Detailed examples

01

Start incidents with session and transaction timelines

pg_stat_activity has one row per server process and exposes session, transaction, query, state-change, wait, and backend transaction timestamps. query_start is not transaction age, and a long-lived idle transaction can retain snapshots or locks even without an active query. Filter out your diagnostic backend and rank by the timestamp relevant to the suspected problem.

Find long active work and idle transactions
SELECT pid, datname, usename, application_name, client_addr, state,
       clock_timestamp() - xact_start AS transaction_age,
       clock_timestamp() - query_start AS query_age,
       wait_event_type, wait_event, left(query, 200) AS query_sample
FROM pg_stat_activity
WHERE pid <> pg_backend_pid()
  AND (state = 'active' OR state = 'idle in transaction')
ORDER BY xact_start NULLS LAST, query_start NULLS LAST;
Back to quick reference ↑
02

Interpret state and wait event together

An active backend can still be waiting, and a non-null wait_event does not by itself prove pathological delay. wait_event_type identifies broad causes such as Lock, IO, Client, or Activity; wait_event provides the specific event. Compare persistence across samples and correlate database waits with operating-system and storage telemetry.

Group current waits without exposing query text
SELECT backend_type, state, wait_event_type, wait_event, count(*) AS sessions
FROM pg_stat_activity
WHERE wait_event IS NOT NULL
GROUP BY backend_type, state, wait_event_type, wait_event
ORDER BY sessions DESC, wait_event_type, wait_event;
Back to quick reference ↑
03

Resolve blocking relationships before considering intervention

pg_blocking_pids returns hard and soft blockers recognized by the lock manager and is safer than hand-joining pg_locks for a first view. A blocked backend can have multiple blockers and chains can form. Identify owners, applications, transaction ages, and business operations before using pg_cancel_backend or pg_terminate_backend; termination rolls back the target transaction and can amplify load.

Map blocked sessions to their direct blockers
SELECT blocked.pid AS blocked_pid,
       blocked.application_name AS blocked_app,
       blocker.pid AS blocker_pid,
       blocker.application_name AS blocker_app,
       blocker.state AS blocker_state,
       clock_timestamp() - blocker.xact_start AS blocker_xact_age
FROM pg_stat_activity AS blocked
CROSS JOIN LATERAL unnest(pg_blocking_pids(blocked.pid)) AS b(pid)
JOIN pg_stat_activity AS blocker ON blocker.pid = b.pid
ORDER BY blocker_xact_age DESC NULLS LAST;
Back to quick reference ↑
04

Use normalized statement statistics for workload ranking

pg_stat_statements aggregates planning and execution metrics by query identity, database, user, and top-level status. It must be loaded through shared_preload_libraries and enabled with CREATE EXTENSION. Totals identify workload impact while means and percentiles answer different questions; counter resets, evictions, configuration, parallel workers, and optional I/O timing affect interpretation.

Rank substantial statements by total execution time
SELECT queryid, calls, rows,
       round(total_exec_time::numeric, 1) AS total_exec_ms,
       round(mean_exec_time::numeric, 2) AS mean_exec_ms,
       shared_blks_read, shared_blks_hit, temp_blks_written,
       left(query, 240) AS query_sample
FROM pg_stat_statements
WHERE calls >= 20
ORDER BY total_exec_time DESC
LIMIT 20;
Back to quick reference ↑
05

Convert cumulative counters into rates over known intervals

Views such as pg_stat_database and pg_stat_io accumulate counters since their reported reset time. Subtract two timestamped samples to obtain rates; a single lifetime total cannot explain a short incident. A buffer hit is not necessarily a physical storage read, and timing columns are meaningful only when the corresponding track_io_timing setting is enabled.

Inspect database totals and their reset horizon
SELECT datname, xact_commit, xact_rollback, tup_returned, tup_fetched,
       blks_read, blks_hit,
       round(blks_hit::numeric / NULLIF(blks_hit + blks_read, 0), 4) AS hit_fraction,
       temp_files, temp_bytes, deadlocks, stats_reset
FROM pg_stat_database
WHERE datname IS NOT NULL
ORDER BY datname;

SELECT backend_type, object, context, reads, read_time, writes, write_time
FROM pg_stat_io
ORDER BY backend_type, object, context;
Back to quick reference ↑
06

Use operation-specific progress views without assuming linear completion

PostgreSQL reports progress for selected commands including VACUUM, ANALYZE, COPY, CREATE INDEX, CLUSTER, and base backup. Available counters and phases differ by command; some phases have no meaningful total, and work estimates can change. Join relation OIDs to regclass for readable output and alert on stalled change across samples rather than one percentage.

Observe running vacuum phases
SELECT pid, datname, relid::regclass AS relation, phase,
       heap_blks_scanned, heap_blks_total,
       round(100.0 * heap_blks_scanned / NULLIF(heap_blks_total, 0), 1) AS heap_scan_pct,
       index_vacuum_count, dead_tuple_bytes, num_dead_item_ids
FROM pg_stat_progress_vacuum
ORDER BY pid;
Back to quick reference ↑
07

Monitor replication by durability position and retained WAL

pg_stat_replication shows one row per WAL sender and its sent, written, flushed, and replayed positions. Byte lag measures retained work, not elapsed recovery time; low write volume can make time-based lag columns stale or null. Inspect replication slots because an inactive slot can retain WAL until storage fills, subject to max_slot_wal_keep_size and slot invalidation state.

Inspect standby positions and physical slot health
SELECT application_name, client_addr, state, sync_state,
       pg_wal_lsn_diff(pg_current_wal_lsn(), sent_lsn) AS send_byte_lag,
       pg_wal_lsn_diff(pg_current_wal_lsn(), replay_lsn) AS replay_byte_lag,
       write_lag, flush_lag, replay_lag
FROM pg_stat_replication
ORDER BY application_name;

SELECT slot_name, slot_type, active, restart_lsn, wal_status, safe_wal_size
FROM pg_replication_slots
ORDER BY slot_name;
Back to quick reference ↑
08

Preserve evidence, privacy, and snapshot semantics

Cumulative statistics may be cached within a transaction according to stats_fetch_consistency; pg_stat_clear_snapshot forces later accesses to refetch. Keep collectors out of long transactions. Query text, client addresses, and application names can contain sensitive data, while visibility is restricted for ordinary roles; grant predefined monitoring roles narrowly and avoid publishing raw SQL to broad dashboards.

Collect a fresh, minimally sensitive sample
SELECT pg_stat_clear_snapshot();
SELECT clock_timestamp() AS sampled_at, datname, backend_type, state,
       wait_event_type, wait_event, count(*) AS sessions
FROM pg_stat_activity
GROUP BY datname, backend_type, state, wait_event_type, wait_event
ORDER BY datname, backend_type, state;
Back to quick reference ↑

Sources and further reading

References

Authoritative documentation used to verify and expand this cheat sheet.

  1. PostgreSQL Global Development GroupPostgreSQL: Monitoring Database Activitypostgresql.org
  2. PostgreSQL Global Development GroupPostgreSQL: The Cumulative Statistics Systempostgresql.org
  3. PostgreSQL Global Development GroupPostgreSQL: Progress Reportingpostgresql.org
  4. PostgreSQL Global Development GroupPostgreSQL: pg_stat_statementspostgresql.org
  5. PostgreSQL Global Development GroupPostgreSQL: System Administration Functionspostgresql.org

Help us improve

Found a typo or missing example?

Tell us what would make this cheat sheet clearer, more complete, or more useful.

Share feedback