Performance Monitoring (PG Extension)
Enabling
Enable native performance monitoring with pg_stat_statement
.
Enable extension
-- Enable extension (once per database)
CREATE EXTENSION IF NOT EXISTS pg_stat_statements;
Add configuration (Non-docker)
Add to postgresql.conf
.
shared_preload_libraries = 'pg_stat_statements'
pg_stat_statements.max = 10000
pg_stat_statements.track = all
Then restart postgresql to activate it.
Add configuration (Docker)
For docker-compose images you can add to the postgres service:
services:
db:
image: postgres:xx-alpine
...
command: >
postgres
-c shared_preload_libraries=pg_stat_statements
-c pg_stat_statements.track=all
-c pg_stat_statements.max=10000
-c track_activity_query_size=2048
Check extension
-- Check if it's loaded
SELECT * FROM pg_extension WHERE extname = 'pg_stat_statements';
Monitoring Queries
Slowest & most frequent
SELECT
query,
calls,
total_time,
mean_time,
rows,
(total_time / calls) AS avg_time
FROM pg_stat_statements
ORDER BY total_time DESC
LIMIT 10;
Monitor real-time query activity
SELECT
pid,
usename,
datname,
state,
wait_event_type,
wait_event,
query_start,
NOW() - query_start AS runtime,
query
FROM pg_stat_activity
WHERE state <> 'idle'
ORDER BY runtime DESC;
Check buffer & cache usage (I/O health)
SELECT
relname AS table_name,
heap_blks_read,
heap_blks_hit,
ROUND(100 * heap_blks_hit / NULLIF(heap_blks_hit + heap_blks_read, 0), 2) AS cache_hit_ratio
FROM pg_statio_user_tables
ORDER BY cache_hit_ratio ASC;
Monitor locks & blocking
-- See current locks
SELECT
pid,
relation::regclass,
mode,
granted,
query
FROM pg_locks
JOIN pg_stat_activity USING (pid)
WHERE relation IS NOT NULL;
-- See blocking chains
SELECT blocked_locks.pid AS blocked_pid,
blocked_activity.query AS blocked_query,
blocking_locks.pid AS blocking_pid,
blocking_activity.query AS blocking_query
FROM pg_locks blocked_locks
JOIN pg_stat_activity blocked_activity
ON blocked_activity.pid = blocked_locks.pid
JOIN pg_locks blocking_locks
ON blocking_locks.locktype = blocked_locks.locktype
AND blocking_locks.database IS NOT DISTINCT FROM blocked_locks.database
AND blocking_locks.relation IS NOT DISTINCT FROM blocked_locks.relation
AND blocking_locks.page IS NOT DISTINCT FROM blocked_locks.page
AND blocking_locks.tuple IS NOT DISTINCT FROM blocked_locks.tuple
AND blocking_locks.virtualxid IS NOT DISTINCT FROM blocked_locks.virtualxid
AND blocking_locks.transactionid IS NOT DISTINCT FROM blocked_locks.transactionid
AND blocking_locks.classid IS NOT DISTINCT FROM blocked_locks.classid
AND blocking_locks.objid IS NOT DISTINCT FROM blocked_locks.objid
AND blocking_locks.objsubid IS NOT DISTINCT FROM blocked_locks.objsubid
AND blocking_locks.pid != blocked_locks.pid
JOIN pg_stat_activity blocking_activity
ON blocking_activity.pid = blocking_locks.pid
WHERE NOT blocked_locks.granted;
Temp file usage (memory under pressure)
SELECT
relname,
last_autovacuum,
n_tup_ins,
n_tup_upd,
n_tup_del,
n_dead_tup,
vacuum_count,
autovacuum_count
FROM pg_stat_user_tables
ORDER BY n_dead_tup DESC;
Database load stats
SELECT
datname,
numbackends,
xact_commit,
xact_rollback,
blks_read,
blks_hit,
tup_returned,
tup_fetched
FROM pg_stat_database
ORDER BY xact_commit DESC;
Last updated