**Purpose**: Troubleshoot database performance, slow queries, deadlocks, and connection issues.
Diagnose and fix database performance issues like slow queries, deadlocks, and connection pool exhaustion. Use this when you see high API latency, database CPU spikes, or "too many connections" errors. Provides specific SQL commands for PostgreSQL and MySQL to identify bottlenecks, missing indexes, and blocking queries.
/plugin marketplace add anton-abyzov/specweave/plugin install sw-infra@specweavePurpose: Troubleshoot database performance, slow queries, deadlocks, and connection issues.
Symptoms:
Diagnosis:
-- Set slow query threshold (1 second)
ALTER SYSTEM SET log_min_duration_statement = 1000;
SELECT pg_reload_conf();
-- Check slow query log
-- /var/log/postgresql/postgresql.log
-- Enable slow query log
SET GLOBAL slow_query_log = 'ON';
SET GLOBAL long_query_time = 1;
-- Check slow query log
-- /var/log/mysql/mysql-slow.log
-- PostgreSQL
EXPLAIN ANALYZE
SELECT users.*, posts.*
FROM users
LEFT JOIN posts ON posts.user_id = users.id
WHERE users.last_login_at > NOW() - INTERVAL '30 days';
-- Look for:
-- - Seq Scan (sequential scan = BAD for large tables)
-- - High cost numbers
-- - High actual time
Red flags in EXPLAIN output:
Example Bad Query:
Seq Scan on users (cost=0.00..100000 rows=10000000)
Filter: (last_login_at > '2025-09-26'::date)
Rows Removed by Filter: 9900000
→ Missing index on last_login_at
-- PostgreSQL: Find missing indexes
SELECT
schemaname,
tablename,
seq_scan,
seq_tup_read,
idx_scan,
seq_tup_read / seq_scan AS avg_seq_read
FROM pg_stat_user_tables
WHERE seq_scan > 0
ORDER BY seq_tup_read DESC
LIMIT 20;
-- Tables with high seq_scan and low idx_scan need indexes
-- PostgreSQL (CONCURRENTLY = no table lock)
CREATE INDEX CONCURRENTLY idx_users_last_login_at
ON users(last_login_at);
-- Verify index is used
EXPLAIN ANALYZE
SELECT * FROM users WHERE last_login_at > NOW() - INTERVAL '30 days';
-- Should show: Index Scan using idx_users_last_login_at
Impact:
Symptoms:
Diagnosis:
-- Check currently locked 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
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.pid != blocked_locks.pid
JOIN pg_catalog.pg_stat_activity blocking_activity ON blocking_activity.pid = blocking_locks.pid
WHERE NOT blocked_locks.granted;
-- Show InnoDB status (includes deadlock info)
SHOW ENGINE INNODB STATUS\G
-- Look for "LATEST DETECTED DEADLOCK" section
-- Pattern 1: Lock order mismatch
-- Transaction 1:
BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
UPDATE accounts SET balance = balance + 100 WHERE id = 2;
COMMIT;
-- Transaction 2 (runs concurrently):
BEGIN;
UPDATE accounts SET balance = balance - 50 WHERE id = 2; -- Locks id=2
UPDATE accounts SET balance = balance + 50 WHERE id = 1; -- Waits for id=1 (deadlock!)
COMMIT;
Fix: Always lock in same order
-- Both transactions lock in order: id=1, then id=2
BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE id = LEAST(1, 2);
UPDATE accounts SET balance = balance + 100 WHERE id = GREATEST(1, 2);
COMMIT;
-- PostgreSQL: Kill blocking query
SELECT pg_terminate_backend(<blocking_pid>);
-- PostgreSQL: Kill idle transactions
SELECT pg_terminate_backend(pid)
FROM pg_stat_activity
WHERE state = 'idle in transaction'
AND state_change < NOW() - INTERVAL '5 minutes';
Symptoms:
Diagnosis:
-- Count connections by state
SELECT state, count(*)
FROM pg_stat_activity
GROUP BY state;
-- Show all connections
SELECT pid, usename, application_name, state, query
FROM pg_stat_activity
WHERE state != 'idle';
-- Check max connections
SHOW max_connections;
-- Show all connections
SHOW PROCESSLIST;
-- Count connections by state
SELECT state, COUNT(*)
FROM information_schema.processlist
GROUP BY state;
-- Check max connections
SHOW VARIABLES LIKE 'max_connections';
Red flags:
-- PostgreSQL: Kill idle connections
SELECT pg_terminate_backend(pid)
FROM pg_stat_activity
WHERE state = 'idle'
AND state_change < NOW() - INTERVAL '10 minutes';
-- Increase max_connections (temporary)
ALTER SYSTEM SET max_connections = 200;
SELECT pg_reload_conf();
Long-term Fix:
Symptoms:
Diagnosis:
-- Top queries by total time
SELECT
query,
calls,
total_exec_time,
mean_exec_time,
max_exec_time
FROM pg_stat_statements
ORDER BY total_exec_time DESC
LIMIT 10;
-- Requires: CREATE EXTENSION pg_stat_statements;
-- Enable performance schema
SET GLOBAL performance_schema = ON;
-- Top queries by execution time
SELECT
DIGEST_TEXT,
COUNT_STAR,
SUM_TIMER_WAIT,
AVG_TIMER_WAIT
FROM performance_schema.events_statements_summary_by_digest
ORDER BY SUM_TIMER_WAIT DESC
LIMIT 10;
Common causes:
Mitigation:
Symptoms:
Diagnosis:
# Linux
df -h
# Database data directory
du -sh /var/lib/postgresql/data/*
du -sh /var/lib/mysql/*
# Find large tables
# PostgreSQL:
SELECT
schemaname,
tablename,
pg_size_pretty(pg_total_relation_size(schemaname||'.'||tablename)) AS size
FROM pg_tables
ORDER BY pg_total_relation_size(schemaname||'.'||tablename) DESC
LIMIT 20;
# 1. Clean up logs
rm /var/log/postgresql/postgresql-*.log.1
rm /var/log/mysql/mysql-slow.log.1
# 2. Vacuum database (PostgreSQL)
VACUUM FULL;
# 3. Archive old data
# Move old records to archive table or backup
# 4. Expand disk (cloud)
# AWS: Modify EBS volume size
# Azure: Expand managed disk
Symptoms:
Diagnosis:
-- On primary:
SELECT * FROM pg_stat_replication;
-- On replica:
SELECT
now() - pg_last_xact_replay_timestamp() AS replication_lag;
-- On replica:
SHOW SLAVE STATUS\G
-- Look for: Seconds_Behind_Master
Red flags:
Common causes:
Mitigation:
Query Performance:
Resource Usage:
Availability:
When diagnosing slow database:
Tools:
EXPLAIN ANALYZEpg_stat_statements (PostgreSQL)pg_stat_activity (PostgreSQL)SHOW PROCESSLIST (MySQL)// BAD: N+1 queries
const users = await db.query('SELECT * FROM users');
for (const user of users) {
const posts = await db.query('SELECT * FROM posts WHERE user_id = ?', [user.id]);
}
// 1 query + N queries = N+1
// GOOD: Single query with JOIN
const usersWithPosts = await db.query(`
SELECT users.*, posts.*
FROM users
LEFT JOIN posts ON posts.user_id = users.id
`);
-- BAD: Fetches all columns (inefficient)
SELECT * FROM users WHERE id = 1;
-- GOOD: Fetch only needed columns
SELECT id, name, email FROM users WHERE id = 1;
-- BAD: No index on frequently queried column
SELECT * FROM users WHERE email = 'user@example.com';
-- Seq Scan on users
-- GOOD: Add index
CREATE INDEX idx_users_email ON users(email);
-- Index Scan using idx_users_email
// BAD: Long transaction holding locks
BEGIN;
const user = await db.query('SELECT * FROM users WHERE id = 1 FOR UPDATE');
await sendEmail(user.email); // External API call (slow!)
await db.query('UPDATE users SET last_email_sent = NOW() WHERE id = 1');
COMMIT;
// GOOD: Keep transactions short
const user = await db.query('SELECT * FROM users WHERE id = 1');
await sendEmail(user.email); // Outside transaction
await db.query('UPDATE users SET last_email_sent = NOW() WHERE id = 1');
Designs feature architectures by analyzing existing codebase patterns and conventions, then providing comprehensive implementation blueprints with specific files to create/modify, component designs, data flows, and build sequences