1) Triage & baseline- Capture current metrics: top SQL by CPU (pg_stat_statements or MySQL slow log), total DB CPU, query frequency, p95/p99 latency.- Snapshot EXPLAIN ANALYZE and collect a few actual runtime traces.2) Profiling (EXPLAIN ANALYZE)- Run EXPLAIN (ANALYZE, BUFFERS, VERBOSE) <query>; capture actual vs planner estimates, node-level times, and buffer/io counts.- Look for sequential scans, large nested-loop joins, high-decode time, or repeated sorts/hash spills.Example:sql
EXPLAIN (ANALYZE, BUFFERS, FORMAT TEXT)
SELECT ... FROM orders JOIN customers ON ... WHERE ...;
- Action: identify hottest operators (e.g., Seq Scan on orders = 80% of time).3) Indexing- Add supporting indexes for WHERE, JOIN, ORDER BY. Prefer covering indexes to avoid lookups.- Test with hypothetical indexes (Postgres pg_hint_plan or CREATE INDEX CONCURRENTLY in staging).- Re-run EXPLAIN ANALYZE to confirm index is used and reduces rows processed.4) Query rewrite- Simplify: remove SELECT *, push predicates earlier, rewrite correlated subqueries to joins, replace DISTINCT with window functions if appropriate.- Materialize expensive subquery once (WITH ... MATERIALIZED in Postgres or temp tables).5) Caching approaches- Short-term: result caching in app or Redis for idempotent reads with TTL based on staleness tolerance.- DB-level: set up prepared statements, statement_plan_cache; use pg_prewarm for frequently used pages.- Validate cache hit rate and eviction effects.6) Denormalization & precomputation- Create summary tables, aggregate rollups, or maintain incremental materialized views updated via triggers or background jobs (logical replication/CDC to analytics store).7) Sharding / scaling- If single-query load is due to data volume, consider horizontal partitioning (range/hash partitioning) to prune partitions on query.- For read-heavy scale: add read replicas and route read traffic; ensure replicas can serve necessary data (replica lag considerations).- As last resort, application-level sharding for hottest tables.8) Estimating & validating cost savings- Quantify baseline: measure avg CPU-seconds per query = (query_time * avg_concurrency). Multiply by qps to get CPU core-seconds per minute.- After change, run load test or A/B rollout to measure new CPU-seconds, latency, and error rates.- Calculate savings = (baseline CPU-seconds - new CPU-seconds) / baseline.- Validate on staging with representative data and on canary production (5-10% traffic) while monitoring SLOs, replica lag, cache hit rates, and rollback safety.9) Operationalize- Add monitoring dashboards and alerts for query latency, DB CPU, slow-query count, cache hit rate, replica lag.- Document changes, migration steps (index creation concurrently), and rollback plan.Example validation metric: if query was 40% DB CPU and optimization reduces per-query CPU by 5x while qps unchanged, expected DB CPU ~40%/5 = 8% → frees ~32% CPU capacity; confirm with 24–72h of production telemetry and cost accounting before full rollout.