/ scripts / pg-health-check.sh
pg-health-check.sh
 1  #!/bin/sh
 2  # pg-health-check.sh — PostgreSQL performance health check
 3  #
 4  # Run: sh scripts/pg-health-check.sh
 5  # Or add to cron jobs for regular monitoring.
 6  
 7  set -eu
 8  
 9  DB="mmo"
10  
11  echo "=== PostgreSQL Health Check ==="
12  echo "Time: $(date -u '+%Y-%m-%d %H:%M:%S UTC')"
13  echo ""
14  
15  # 1. Connection status
16  echo "--- Connections ---"
17  psql -d "$DB" -c "
18  SELECT state, count(*)
19  FROM pg_stat_activity
20  WHERE datname = '$DB'
21  GROUP BY state
22  ORDER BY count DESC;
23  "
24  
25  # 2. Table sizes
26  echo "--- Top 10 Tables by Size ---"
27  psql -d "$DB" -c "
28  SELECT schemaname || '.' || relname AS table,
29         pg_size_pretty(pg_total_relation_size(schemaname || '.' || relname)) AS total_size,
30         n_live_tup AS live_rows
31  FROM pg_stat_user_tables
32  ORDER BY pg_total_relation_size(schemaname || '.' || relname) DESC
33  LIMIT 10;
34  "
35  
36  # 3. Missing indexes (sequential scans on large tables)
37  echo "--- Potential Missing Indexes (seq scans on large tables) ---"
38  psql -d "$DB" -c "
39  SELECT schemaname || '.' || relname AS table,
40         seq_scan, seq_tup_read,
41         idx_scan, n_live_tup,
42         ROUND(100.0 * seq_scan / GREATEST(seq_scan + idx_scan, 1), 1) AS seq_pct
43  FROM pg_stat_user_tables
44  WHERE n_live_tup > 1000
45    AND seq_scan > idx_scan
46  ORDER BY seq_tup_read DESC
47  LIMIT 10;
48  "
49  
50  # 4. Table bloat / autovacuum
51  echo "--- Autovacuum Status (tables with most dead tuples) ---"
52  psql -d "$DB" -c "
53  SELECT schemaname || '.' || relname AS table,
54         n_dead_tup, n_live_tup,
55         ROUND(100.0 * n_dead_tup / GREATEST(n_live_tup, 1), 1) AS dead_pct,
56         last_autovacuum::text,
57         last_autoanalyze::text
58  FROM pg_stat_user_tables
59  WHERE n_dead_tup > 100
60  ORDER BY n_dead_tup DESC
61  LIMIT 10;
62  "
63  
64  # 5. Slowest queries (requires pg_stat_statements)
65  echo "--- Top 5 Slowest Queries (by mean time) ---"
66  psql -d "$DB" -c "
67  SELECT ROUND(mean_exec_time::numeric, 2) AS mean_ms,
68         calls,
69         ROUND(total_exec_time::numeric, 0) AS total_ms,
70         LEFT(query, 80) AS query_preview
71  FROM pg_stat_statements
72  WHERE dbid = (SELECT oid FROM pg_database WHERE datname = '$DB')
73    AND calls > 5
74  ORDER BY mean_exec_time DESC
75  LIMIT 5;
76  " 2>/dev/null || echo "(pg_stat_statements not available)"
77  
78  # 6. Database size
79  echo "--- Database Size ---"
80  psql -d "$DB" -c "SELECT pg_size_pretty(pg_database_size('$DB')) AS database_size;"
81  
82  echo ""
83  echo "=== Health check complete ==="