Module 09 · Lesson 01

Why is it slow? A method, not a hunch

Measure the layer, then the hop, then the line of code. Guessing wastes the incident.

Advanced 8 min read Updated 2026-11-02

What you will be able to do

  • Turn a vague slowness report into a measurable symptom
  • Bisect a request path with traces and timings
  • Identify N+1 queries and missing indexes from evidence
  • Distinguish saturation from contention from a slow dependency

Start with a precise symptom

Before touching anything, write four facts: which endpoint, which percentile, which time window, and what the number was before. "Checkout is slow" becomes "checkout p99 went from 400 ms to 2.1 s starting at 14:05, on all instances". The second statement already narrows the search.

ObservationImplies
All endpoints slow, all instancesDatabase, network or shared dependency
One endpoint slowThe code path or its query
One instance slowNoisy neighbour, bad host, GC, disk
Slow at high traffic onlyQueueing or contention, not raw work
Slow for large accounts onlyQuery shape, N+1, or fan-out
Slow after a deployNew code path, migration, config

Bisect the path

A trace turns five guesses into one. If the waterfall says 40 ms in the gateway and 1,900 ms inside the cart service, the answer is in that service — not the CDN, not DNS, not the load balancer. Repeated across a sample of slow requests, the pattern becomes obvious.

cart-service  1,900 ms
  db.query    1,820 ms   (312 calls)     <-- N+1
  redis.get      12 ms
  serialize       8 ms

Three hundred and twelve queries for one request is the N+1 signature: fetch a list, then fetch something for each item. The fix is a single batched query. It is rarely a caching problem, and adding a cache to hide it also caches the inefficiency.

Do not optimise blind
Adding an index to a query that runs twice a day and removing a 300-query loop are not the same win. Sort by time saved, not by how clever the change feels.

Saturation versus contention

  • Saturation: CPU, connections, disk or network near its limit. Latency rises because work waits in a queue. Fix by adding capacity or reducing work.
  • Contention: threads wait on a lock, a row, or a single hot key. CPU is idle but throughput is low. Fix by partitioning or shortening the critical section.
  • Slow dependency: a downstream call takes a fixed long time. Fix with a timeout, caching, or async work — not with more application threads.
  • Pathological input: one request triggers a scan or a huge payload. Fix with limits at the edge before it reaches the service.
Index lookup
< 1 ms
B-tree, warm cache
Unindexed scan, 10M rows
seconds
Often the entire latency budget
Same-region RPC
0.5-2 ms
312 of them is 600 ms of pure overhead
Connection wait
10-100 ms
Appears as database latency but is pool exhaustion
Quantify
One route, one percentile, one baseline. Write the target down.
Locate
Sample ten slow traces and find the hop that dominates.
Explain
Check query count, index usage, payload size, lock waits. A hypothesis must predict a number.
Fix and re-measure
Change one thing, measure the same percentile on the same route. Revert if the number does not move.
How to answer a latency question
"I'd compare p99 on that route against its baseline, sample traces to find the dominant span, and check query count against rows returned. If it scales with the result set, it's N+1 or a missing index before it's a caching problem."

Worth remembering

  • "Slow" is not a symptom. p99 latency, on one route, at one time, compared to a baseline is.
  • A trace waterfall finds the hop in one request; aggregates only tell you which service.
  • Latency that grows with the number of rows is a query shape problem, almost always N+1 or a missing index.
  • Saturation shows queueing; contention shows throughput collapsing while CPU is idle.

Checkpoint

Answer all 3 questions to finish the lesson.
1. One endpoint is slow; every other endpoint is normal and all instances are affected. Where do you look first?
The scope is the endpoint, so shared infrastructure is ruled out. The difference is in that specific code path.
2. A trace shows 312 database queries for one request. What is it?
One query per item in a result set is the N+1 signature. Batching removes the per-item round trips.
3. Throughput dropped sharply while CPU is mostly idle. Which explanation fits?
Idle CPU with low throughput means work is waiting, not computing. Lock or row contention serializes the path.