|
| 1 | +--- |
| 2 | +name: db-diagnose |
| 3 | +description: Diagnose database issues like TOAST bloat, idle transactions, dead tuples, and disk usage on the Subsquid Cloud PostgreSQL database. Use when the database is slow, bloated, unresponsive, or running out of space. |
| 4 | +--- |
| 5 | + |
| 6 | +# Database Diagnostics |
| 7 | + |
| 8 | +You are diagnosing database issues for the origin-squid project deployed on Subsquid Cloud. |
| 9 | + |
| 10 | +## Step 1: Get Database Credentials |
| 11 | + |
| 12 | +Run this to get the Subsquid Cloud database connection info: |
| 13 | + |
| 14 | +```bash |
| 15 | +npx sqd view -o origin -n origin-squid -t prod --json |
| 16 | +``` |
| 17 | + |
| 18 | +Parse the JSON output to extract the postgres connection params from `addons.postgres.connections`. Prefer the read/write connection (not read-only) since some diagnostics need it. |
| 19 | + |
| 20 | +Construct a `psql` connection string or use individual params. For running queries use: |
| 21 | + |
| 22 | +```bash |
| 23 | +PGPASSWORD=<password> psql -h <host> -p <port> -U <user> -d <database> |
| 24 | +``` |
| 25 | + |
| 26 | +## Step 2: Run Diagnostics |
| 27 | + |
| 28 | +Run ALL of these diagnostic queries and report the results: |
| 29 | + |
| 30 | +### TOAST Table Sizes (the most common issue) |
| 31 | +```sql |
| 32 | +SELECT |
| 33 | + n.nspname AS schema_name, |
| 34 | + c.relname AS table_name, |
| 35 | + t.relname AS toast_table, |
| 36 | + pg_size_pretty(pg_relation_size(t.oid)) AS toast_size, |
| 37 | + pg_size_pretty(pg_relation_size(c.oid)) AS main_size, |
| 38 | + pg_size_pretty(pg_total_relation_size(c.oid)) AS total_size |
| 39 | +FROM pg_class c |
| 40 | +JOIN pg_class t ON t.oid = c.reltoastrelid |
| 41 | +JOIN pg_namespace n ON n.oid = c.relnamespace |
| 42 | +WHERE t.relkind = 't' |
| 43 | +ORDER BY pg_relation_size(t.oid) DESC |
| 44 | +LIMIT 20; |
| 45 | +``` |
| 46 | + |
| 47 | +### Idle/Long-Running Transactions (block VACUUM from reclaiming space) |
| 48 | +```sql |
| 49 | +SELECT pid, state, now() - xact_start AS tx_age, |
| 50 | + now() - query_start AS query_age, |
| 51 | + left(query, 150) AS query |
| 52 | +FROM pg_stat_activity |
| 53 | +WHERE xact_start IS NOT NULL |
| 54 | +ORDER BY xact_start ASC |
| 55 | +LIMIT 20; |
| 56 | +``` |
| 57 | + |
| 58 | +### Dead Tuple Counts (tables needing VACUUM) |
| 59 | +```sql |
| 60 | +SELECT schemaname, relname, |
| 61 | + n_dead_tup, n_live_tup, |
| 62 | + round(n_dead_tup::numeric / greatest(n_live_tup, 1) * 100, 1) AS dead_pct, |
| 63 | + last_vacuum, last_autovacuum |
| 64 | +FROM pg_stat_user_tables |
| 65 | +WHERE n_dead_tup > 10000 |
| 66 | +ORDER BY n_dead_tup DESC |
| 67 | +LIMIT 20; |
| 68 | +``` |
| 69 | + |
| 70 | +### VACUUM Horizon (if old, something is holding it back) |
| 71 | +```sql |
| 72 | +SELECT datname, datfrozenxid, age(datfrozenxid) AS xid_age |
| 73 | +FROM pg_database WHERE datname = current_database(); |
| 74 | +``` |
| 75 | + |
| 76 | +### Overall Disk Usage |
| 77 | +```sql |
| 78 | +SELECT |
| 79 | + n.nspname AS schema_name, |
| 80 | + c.relname AS table_name, |
| 81 | + pg_size_pretty(pg_total_relation_size(c.oid)) AS total_size, |
| 82 | + pg_size_pretty(pg_relation_size(c.oid)) AS table_size, |
| 83 | + pg_size_pretty(pg_indexes_size(c.oid)) AS index_size |
| 84 | +FROM pg_class c |
| 85 | +JOIN pg_namespace n ON n.oid = c.relnamespace |
| 86 | +WHERE c.relkind = 'r' |
| 87 | + AND n.nspname NOT IN ('pg_catalog', 'information_schema', 'pg_toast') |
| 88 | +ORDER BY pg_total_relation_size(c.oid) DESC |
| 89 | +LIMIT 20; |
| 90 | +``` |
| 91 | + |
| 92 | +### Active Locks |
| 93 | +```sql |
| 94 | +SELECT l.pid, l.mode, l.granted, c.relname, a.state, left(a.query, 100) AS query |
| 95 | +FROM pg_locks l |
| 96 | +JOIN pg_class c ON c.oid = l.relation |
| 97 | +JOIN pg_stat_activity a ON a.pid = l.pid |
| 98 | +WHERE NOT l.granted OR l.mode LIKE '%Exclusive%' |
| 99 | +ORDER BY l.granted, c.relname |
| 100 | +LIMIT 20; |
| 101 | +``` |
| 102 | + |
| 103 | +### Database Size |
| 104 | +```sql |
| 105 | +SELECT pg_size_pretty(pg_database_size(current_database())) AS db_size; |
| 106 | +``` |
| 107 | + |
| 108 | +## Step 3: Analyze and Recommend |
| 109 | + |
| 110 | +Based on the results, identify: |
| 111 | + |
| 112 | +1. **Root cause**: What is causing the issue (idle transactions, TOAST bloat, missing vacuums, etc.) |
| 113 | +2. **Immediate fix**: Commands to run right now (e.g., `pg_terminate_backend(pid)`, `VACUUM FULL`) |
| 114 | +3. **Prevention**: What should be done to prevent recurrence |
| 115 | + |
| 116 | +### Common Fixes |
| 117 | + |
| 118 | +**Kill idle-in-transaction sessions:** |
| 119 | +```sql |
| 120 | +SELECT pg_terminate_backend(pid) |
| 121 | +FROM pg_stat_activity |
| 122 | +WHERE state = 'idle in transaction' |
| 123 | + AND now() - xact_start > interval '2 minutes'; |
| 124 | +``` |
| 125 | + |
| 126 | +**VACUUM FULL a bloated table** (locks table, use with care): |
| 127 | +```sql |
| 128 | +VACUUM FULL VERBOSE "schema_name"."table_name"; |
| 129 | +``` |
| 130 | + |
| 131 | +**Regular VACUUM** (less disruptive, doesn't reclaim TOAST as well): |
| 132 | +```sql |
| 133 | +VACUUM VERBOSE "schema_name"."table_name"; |
| 134 | +``` |
| 135 | + |
| 136 | +## Important Notes |
| 137 | + |
| 138 | +- `VACUUM FULL` takes an ACCESS EXCLUSIVE lock — all queries on that table will block until it finishes. Warn the user before running. |
| 139 | +- Always ask the user before running destructive operations (killing sessions, VACUUM FULL). |
| 140 | +- The mainnet processor has a built-in TOAST vacuum monitor (`src/utils/toast-vacuum.ts`) that checks every 30 minutes and auto-vacuums tables with TOAST > 1GB. |
| 141 | +- Session timeouts are configured at the database level: `idle_in_transaction_session_timeout = 2min`, `idle_session_timeout = 5min`. |
| 142 | +- Use `$ARGUMENTS` for any specific table or issue the user wants to investigate. |
0 commit comments