๐Ÿ˜ PostgreSQL Debugging & Troubleshooting Guide

โ† Back to Home Elasticsearch โ†’ MongoDB โ†’ Kubernetes โ†’

A comprehensive guide for diagnosing and troubleshooting PostgreSQL database issues.

๐Ÿ“‹ Table of Contents


๐Ÿ”Œ Connection & Status

Check PostgreSQL Version

psql -U <USERNAME> -d <DATABASE> -c "SELECT version();"

List All Databases

psql -U <USERNAME> -c "\l"
# Or using SQL
psql -U <USERNAME> -c "SELECT datname, pg_size_pretty(pg_database_size(datname)) as size FROM pg_database ORDER BY pg_database_size(datname) DESC;"

Check Active Connections

-- View all active connections
SELECT pid, usename, application_name, client_addr, state, query_start, query 
FROM pg_stat_activity 
WHERE state = 'active';

-- Count connections per database
SELECT datname, count(*) as connections 
FROM pg_stat_activity 
GROUP BY datname 
ORDER BY connections DESC;

-- Check connection limits
SELECT setting::int as max_connections, 
       count(*) as current_connections,
       setting::int - count(*) as available_connections
FROM pg_settings, pg_stat_activity
WHERE name = 'max_connections'
GROUP BY setting;

Test Database Connection

# Basic connection test
psql -U <USERNAME> -h <HOST> -d <DATABASE> -c "SELECT 1;"

# Connection with timeout
timeout 5 psql -U <USERNAME> -h <HOST> -d <DATABASE> -c "SELECT now();"

โšก Performance Diagnostics

Check Database Size

-- All databases with sizes
SELECT 
    datname as database,
    pg_size_pretty(pg_database_size(datname)) as size,
    pg_database_size(datname) as size_bytes
FROM pg_database 
ORDER BY pg_database_size(datname) DESC;

-- Table sizes in current database
SELECT 
    schemaname,
    tablename,
    pg_size_pretty(pg_total_relation_size(schemaname||'.'||tablename)) as total_size,
    pg_size_pretty(pg_relation_size(schemaname||'.'||tablename)) as table_size,
    pg_size_pretty(pg_total_relation_size(schemaname||'.'||tablename) - pg_relation_size(schemaname||'.'||tablename)) as indexes_size
FROM pg_tables 
WHERE schemaname NOT IN ('pg_catalog', 'information_schema')
ORDER BY pg_total_relation_size(schemaname||'.'||tablename) DESC 
LIMIT 20;

Check Cache Hit Ratio

-- Should be > 99% for optimal performance
SELECT 
    sum(heap_blks_read) as heap_read,
    sum(heap_blks_hit) as heap_hit,
    sum(heap_blks_hit) / (sum(heap_blks_hit) + sum(heap_blks_read)) * 100 as cache_hit_ratio
FROM pg_statio_user_tables;

-- Index cache hit ratio
SELECT 
    sum(idx_blks_read) as idx_read,
    sum(idx_blks_hit) as idx_hit,
    sum(idx_blks_hit) / nullif(sum(idx_blks_hit) + sum(idx_blks_read), 0) * 100 as idx_cache_hit_ratio
FROM pg_statio_user_indexes;

Check Table Bloat

SELECT 
    schemaname,
    tablename,
    pg_size_pretty(pg_total_relation_size(schemaname||'.'||tablename)) as size,
    n_dead_tup as dead_tuples,
    n_live_tup as live_tuples,
    round(n_dead_tup * 100.0 / NULLIF(n_live_tup + n_dead_tup, 0), 2) as dead_ratio
FROM pg_stat_user_tables
WHERE n_dead_tup > 1000
ORDER BY n_dead_tup DESC
LIMIT 20;

Vacuum Statistics

-- Last vacuum and analyze times
SELECT 
    schemaname,
    relname,
    last_vacuum,
    last_autovacuum,
    last_analyze,
    last_autoanalyze,
    n_dead_tup
FROM pg_stat_user_tables
ORDER BY n_dead_tup DESC
LIMIT 20;

๐Ÿ” Query Analysis

Find Slow Running Queries

-- Queries running longer than 5 minutes
SELECT 
    pid,
    now() - query_start as duration,
    usename,
    datname,
    state,
    query
FROM pg_stat_activity
WHERE state = 'active' 
    AND now() - query_start > interval '5 minutes'
ORDER BY duration DESC;

Most Time-Consuming Queries (pg_stat_statements)

-- Enable extension first: CREATE EXTENSION IF NOT EXISTS pg_stat_statements;

SELECT 
    query,
    calls,
    total_exec_time,
    mean_exec_time,
    max_exec_time,
    rows
FROM pg_stat_statements
ORDER BY total_exec_time DESC
LIMIT 20;

-- Queries with highest average execution time
SELECT 
    query,
    calls,
    round(mean_exec_time::numeric, 2) as avg_ms,
    round(total_exec_time::numeric, 2) as total_ms
FROM pg_stat_statements
ORDER BY mean_exec_time DESC
LIMIT 20;

Analyze Query Plan

-- Explain query execution plan
EXPLAIN ANALYZE 
SELECT * FROM your_table WHERE condition;

-- More detailed analysis
EXPLAIN (ANALYZE, BUFFERS, VERBOSE) 
SELECT * FROM your_table WHERE condition;

Sequential Scans (potential missing indexes)

SELECT 
    schemaname,
    tablename,
    seq_scan,
    seq_tup_read,
    idx_scan,
    seq_tup_read / seq_scan as avg_seq_tup_read
FROM pg_stat_user_tables
WHERE seq_scan > 0
ORDER BY seq_scan DESC
LIMIT 20;

Unused Indexes

SELECT 
    schemaname,
    tablename,
    indexname,
    idx_scan as scans,
    pg_size_pretty(pg_relation_size(indexrelid)) as size
FROM pg_stat_user_indexes
WHERE idx_scan = 0
    AND indexrelname NOT LIKE '%_pkey'
ORDER BY pg_relation_size(indexrelid) DESC;

๐Ÿ”„ Replication Monitoring

Check Replication Status (on primary)

SELECT 
    client_addr,
    application_name,
    state,
    sync_state,
    pg_wal_lsn_diff(pg_current_wal_lsn(), replay_lsn) as lag_bytes,
    pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), replay_lsn)) as lag
FROM pg_stat_replication;

Check Replication Lag (on replica)

SELECT 
    now() - pg_last_xact_replay_timestamp() as replication_lag,
    pg_is_in_recovery() as is_replica;

Replication Slots

-- List all replication slots
SELECT 
    slot_name,
    slot_type,
    database,
    active,
    pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn)) as retained_wal
FROM pg_replication_slots;

๐Ÿ› ๏ธ Database Maintenance

Vacuum and Analyze

# Vacuum specific table
psql -U <USERNAME> -d <DATABASE> -c "VACUUM VERBOSE ANALYZE table_name;"

# Vacuum entire database
vacuumdb -U <USERNAME> -d <DATABASE> --analyze --verbose

# Full vacuum (requires exclusive lock)
vacuumdb -U <USERNAME> -d <DATABASE> --full --analyze

Reindex

# Reindex specific table
psql -U <USERNAME> -d <DATABASE> -c "REINDEX TABLE table_name;"

# Reindex entire database
reindexdb -U <USERNAME> -d <DATABASE>

# Reindex concurrently (no locks)
psql -U <USERNAME> -d <DATABASE> -c "REINDEX INDEX CONCURRENTLY index_name;"

Database Statistics

-- Reset statistics
SELECT pg_stat_reset();

-- Update table statistics
ANALYZE table_name;

-- Get table statistics age
SELECT 
    schemaname,
    tablename,
    attname,
    n_distinct,
    correlation
FROM pg_stats
WHERE tablename = 'your_table';

๐Ÿ”’ Locks & Blocking

Check Current Locks

SELECT 
    locktype,
    database,
    relation::regclass,
    page,
    tuple,
    transactionid,
    mode,
    granted
FROM pg_locks
WHERE NOT granted;

Find Blocking Queries

SELECT 
    blocked_locks.pid AS blocked_pid,
    blocked_activity.usename AS blocked_user,
    blocking_locks.pid AS blocking_pid,
    blocking_activity.usename AS blocking_user,
    blocked_activity.query AS blocked_statement,
    blocking_activity.query AS blocking_statement,
    blocked_activity.application_name AS blocked_application
FROM pg_catalog.pg_locks blocked_locks
JOIN pg_catalog.pg_stat_activity blocked_activity ON blocked_activity.pid = blocked_locks.pid
JOIN pg_catalog.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.transactionid IS NOT DISTINCT FROM blocked_locks.transactionid
    AND blocking_locks.pid != blocked_locks.pid
JOIN pg_catalog.pg_stat_activity blocking_activity ON blocking_activity.pid = blocking_locks.pid
WHERE NOT blocked_locks.granted;

Kill Blocking Query

-- Terminate specific connection
SELECT pg_terminate_backend(<PID>);

-- Cancel query (safer than terminate)
SELECT pg_cancel_backend(<PID>);

โš™๏ธ Configuration & Settings

View Current Settings

-- All settings
SELECT name, setting, unit, context 
FROM pg_settings 
ORDER BY name;

-- Specific settings
SELECT name, setting, unit, short_desc 
FROM pg_settings 
WHERE name IN ('max_connections', 'shared_buffers', 'work_mem', 'maintenance_work_mem');

-- Settings that require restart
SELECT name, setting, pending_restart 
FROM pg_settings 
WHERE pending_restart = true;

Check WAL Settings

SELECT name, setting, unit 
FROM pg_settings 
WHERE name LIKE '%wal%' OR name LIKE '%checkpoint%';

Memory Settings

SELECT 
    name,
    setting,
    unit,
    pg_size_pretty(setting::bigint * 
        CASE unit 
            WHEN 'kB' THEN 1024
            WHEN 'MB' THEN 1024*1024
            WHEN '8kB' THEN 8192
            ELSE 1 
        END) as value
FROM pg_settings
WHERE name IN ('shared_buffers', 'effective_cache_size', 'work_mem', 'maintenance_work_mem');

Show Configuration File Location

SELECT name, setting FROM pg_settings WHERE name LIKE '%config%' OR name LIKE '%hba%';

๐Ÿ“Š Additional Diagnostics

Transaction ID Wraparound Check

SELECT 
    datname,
    age(datfrozenxid) as xid_age,
    2147483648 - age(datfrozenxid) as xids_remaining,
    round(100.0 * age(datfrozenxid) / 2147483648, 2) as percent_to_wraparound
FROM pg_database
ORDER BY age(datfrozenxid) DESC;

Check Autovacuum Settings

SELECT 
    name,
    setting,
    short_desc
FROM pg_settings
WHERE name LIKE 'autovacuum%';

Background Writer Statistics

SELECT * FROM pg_stat_bgwriter;

๐Ÿšจ Common Issues & Solutions

Issue: Too Many Connections

-- Check current connections
SELECT count(*) FROM pg_stat_activity;

-- Kill idle connections
SELECT pg_terminate_backend(pid) 
FROM pg_stat_activity 
WHERE state = 'idle' 
    AND state_change < now() - interval '10 minutes';

Issue: Slow Queries

  1. Enable pg_stat_statements extension
  2. Analyze query plans with EXPLAIN ANALYZE
  3. Check for missing indexes
  4. Increase work_mem if needed
  5. Update statistics with ANALYZE

Issue: High Disk Usage

  1. Check table bloat
  2. Run VACUUM FULL on bloated tables
  3. Clean up old WAL files
  4. Archive old data

๐Ÿ“ Notes

๐Ÿ”— Useful Tools


โ† Back to Home Elasticsearch โ†’ MongoDB โ†’ Kubernetes โ†’