Start by clarifying scope: which dashboard/query spike (query id, time window). Then follow a step-by-step triage using logs, metrics, traces, and SQL.1) Establish timeline & baseline- Query the BI tool / query history for the offending query id and timestamps.-- example (postgres): recent runs and durations
SELECT query, state, start_time, end_time, total_time
FROM pg_stat_activity_history
WHERE query LIKE '%<core_query_name>%'
AND start_time > now() - interval '2 hours'
ORDER BY start_time DESC;
Signal: sudden jump in P99 at a specific timestamp vs gradual drift.2) Check system metrics (compute saturation)- Look at CPU, memory, IO, and network around spike (Prometheus/Cloud metrics).# Prometheus example
rate(node_cpu_seconds_total[5m]) by (instance)
node_memory_MemAvailable_bytes
rate(node_disk_io_time_seconds_total[5m]) by (instance)
Signal for (b): high CPU% across DB workers, memory pressure (swap), high disk queue length or high read latency concurrent with query latency spike. If saturation coincides with many queries, suspect resource contention.3) Inspect query traces / EXPLAIN plans (query plan regression)- Pull query plan before and during spike.-- capture recent plan (Postgres)
EXPLAIN (ANALYZE, BUFFERS, FORMAT JSON) <core_query>;
Signal for (c): plan shape changed (nested loop vs hash join), missing index usage, dramatically higher estimated vs actual rows, unexpected sequential scans. If a new plan appears only after stats update or parameter sniffing, that's a regression.4) Look for upstream data skew / cardinality changes- Check distribution and volumes of key partition/join columns over time.SELECT key_col, count(*) AS cnt
FROM source_table
WHERE ingestion_time >= now() - interval '1 day'
GROUP BY key_col
ORDER BY cnt DESC
LIMIT 50;
Signal for (a): one key suddenly dominates (hot partition), extreme growth in table size or skewed join leading to very large intermediate rows; if plan is same but actual rows processed rose hugely.5) Correlate traces and logs- Use APM/tracing (Jaeger/NewRelic) to view span breakdown: which stage spent most time (scan, join, network).Signal mapping:- If spans show database CPU/IO waits and system metrics high → compute saturation (b).- If spans show a particular operator (join/aggregate) exploded in time with plan showing bad operator → plan regression (c).- If spans show disproportionate data transfer from upstream service or a single partition producing huge payloads → data skew (a).6) Quick mitigation experiments- For suspected (c): force replan or add index and compare EXPLAIN.SET enable_nestloop = off; EXPLAIN ANALYZE <core_query>;
- For (a): filter on top keys to confirm latency change; rebalance partitions.- For (b): spin up read-replicas or increase resources, or run the query during low load and compare.7) Produce actionable conclusion with evidence- Attach metrics (CPU/time series), trace spans, before/after EXPLAIN JSON, and sample SQL proving skew. Recommend fix (index, stats vacuum, repartition, scale resources, or query rewrite).Expected signals summary:- (a) skew: same plan, huge actual row counts for specific keys, spike in single partition ingestion/transfer.- (b) saturation: system-wide resource metrics spike, many queries slowed, increased IO wait.- (c) plan regression: changed plan operator or estimates → single-query latency spike without system-wide metric change.