plugins/backend-toolkit/skills/query-optimization/SKILL.md
Find and fix slow Postgres queries — rank by pg_stat_statements, diagnose with EXPLAIN (ANALYZE, BUFFERS), kill N+1 at the ORM layer, add the right index. Use when an endpoint is slow, DB CPU is high, or before scaling traffic. Not for schema/index design from scratch (use schema-design) or result-level caching (use caching-strategy).
npx skillsauth add jaykim88/claude-ai-engineering query-optimizationInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Find the queries that actually cost the most, understand why they're slow from the plan, and fix the root cause (missing index, N+1, bad estimate) — measure-first, never guess.
Universal — the find-rank-diagnose-fix workflow (statement stats → query plan → index/rewrite) applies to any SQL DB; tool names differ.
Rank queries by total cost — measure first
pg_stat_statements ordered by total_exec_time (not single-call time)Diagnose each offender with EXPLAIN (ANALYZE, BUFFERS)
ANALYZE runs the query; BUFFERS shows IO — both needed for truthpg_stat_activity waits before chasing the planDetect N+1 at the ORM layer first
include / findMany batching / DataLoader) before reaching for raw SQLFix by root cause
schema-design)ANALYZE the table / increase statistics target. Multi-column correlations (e.g. WHERE country='KR' AND city='Seoul') need CREATE STATISTICS (extended stats) — single-column stats over-estimate selectivityREINDEX INDEX CONCURRENTLY and tune autovacuumSELECT ... FOR UPDATE SKIP LOCKED for queue patterns, or partition the hot key (cross-ref transaction-management)caching-strategy)Validate (validation loop)
EXPLAIN ANALYZE; if still Seq Scan / still slow → the index isn't being used (check column order, type mismatch, function-wrapping) → adjust and re-runpg_stat_statements after deploy — confirm the query dropped in ranking| ❌ Anti-pattern | ✅ Correct |
|---|---|
| Adding indexes by guessing | pg_stat_statements → EXPLAIN ANALYZE → targeted index |
| Optimizing the single slowest query | Optimize highest total_exec_time (frequency × cost) |
| Fixing N+1 with raw SQL | Fix at ORM layer (batching/eager-load) first |
| EXPLAIN without ANALYZE | EXPLAIN (ANALYZE, BUFFERS) for real timings + IO |
| Index on WHERE lower(email) but querying email | Match index expression to query expression |
| Diagnosing as "slow query" when it's connection-pool saturation | Confirm via pg_stat_activity waits before tuning the plan |
| Bloated index after long heavy writes (silently slow) | REINDEX INDEX CONCURRENTLY; tune autovacuum so it doesn't reach this state |
| Single-column stats for correlated predicates | CREATE STATISTICS (extended stats) for multi-column correlations |
| Tier | Examples | Action SLA | |---|---|---| | Critical | N+1 on a hot endpoint causing timeouts; full table scan on a multi-million row table per request | Fix immediately | | Major | Missing index on a frequent query (high total_exec_time); unbounded query (no LIMIT) | Fix this sprint | | Minor | Suboptimal plan on a rare query; slightly stale table statistics | Schedule within 2 sprints |
pg_stat_statements offenders diagnosedEXPLAIN ANALYZEdocs/query-audit-YYYY-MM-DD.md — top offenders, plans, fixes, before/after timingsperf(db): eliminate N+1 in <endpoint> / perf(db): add index for <query>SELECT * FROM pg_stat_statements ORDER BY total_exec_time DESC LIMIT 10;include / select with relations; avoid per-row findUnique in a loopEXPLAIN (ANALYZE, BUFFERS) <query> in psql / Supabase SQL editorlog: ['query']) in dev to spot N+1?statement_cache_size=0) — silent perf cliff otherwisepg_stat_user_indexes for unused indexes; pgstattuple for bloat; REINDEX INDEX CONCURRENTLY to rebuild without lockselectinload/joinedload for N+1; pg_stat_statements samePreload; same Postgres toolingpg_stat_statements + EXPLAIN ANALYZE are Postgres; MySQL uses performance_schema + EXPLAIN ANALYZE (8.0+)schema-design — the fix is often the right index typeperformance-profiling — query time is usually the top backend bottleneckcaching-strategy — cache the query result when optimization hits its limitpg_stat_statements to rank by total time, then EXPLAIN (ANALYZE, BUFFERS) the offenders looking for Seq Scans / bad row estimates / N+1 before adding indexes. Three senior diagnostics that look like "slow query" but aren't: connection-pool exhaustion, index bloat after heavy writes (re-REINDEX CONCURRENTLY), and correlated-predicate misestimation (needs extended CREATE STATISTICS). PgBouncer transaction-pooling silently breaks server-side prepared statements — pin pooling mode to your driver's assumption.development
Design webhooks correctly on both sides — sending (HMAC signing, retries with backoff, at-least-once) and receiving (verify signature on raw body, enqueue + 200 fast, dedupe on event id). Use when adding webhook delivery or consuming a provider's webhooks. Not for internal service-to-service events (use async-messaging) or general outbound-call retry policy (use resilience-patterns).
testing
Use transactions and isolation levels correctly — keep them short, no network calls inside, explicit isolation, retry on serialization conflicts, and choose optimistic vs pessimistic locking. Use when a write spans multiple tables, when concurrent updates corrupt data, or when designing money/inventory flows. Not for cross-service event delivery (use async-messaging Outbox) or schema-level constraints (use schema-design).
development
Backend testing pyramid — unit for pure logic, integration against a real DB (Testcontainers), and consumer-driven contract testing (Pact) for service boundaries. Use before a feature, after a bug fix, or when services break each other on deploy. Not for load testing (use performance-profiling) or security testing (use backend-security-audit).
data-ai
Design a relational schema — normalize to 3NF then denormalize with justification, choose the right Postgres index type per data shape, enforce constraints at the DB. Use when modeling a new domain, when queries are slow, or before a migration. Not for diagnosing slow queries (use query-optimization) or shipping the change without downtime (use migration-strategy).