Chapter 10 — Production Observability
From "the Alert Fired" to "This Is the Root Cause"
Part 2: Instruments. Prerequisites: Chapters 1–9. Concept background: SD QA Q54 — Metrics, logs and traces and Q55 — Distributed tracing.
Opening
Every instrument so far looks at one layer up close: the browser, the wire, the logs, the process, the database. In production you almost never start there. You start with an alert that says "error rate above 5%" or "p99 latency above 2 s," a dashboard full of lines, and a Slack channel asking what is going on.
Observability is the ability to answer new questions about your system from the outside, using the signals it emits — without shipping new code to ask them. This chapter teaches you to read those signals without being fooled by them, and gives you a repeatable path from the first alert to the root cause. It closes Part 2: after this, you have an instrument for every layer and a way to choose between them.
Part 1: The Signals and What Each One Is For
| Signal | What it is | Its job in an investigation | Its blind spot |
|---|---|---|---|
| Metrics | Numbers aggregated over time: request rate, error rate, latency percentiles, CPU, pool usage | Detect that something is wrong, when it started, and how big it is | They tell you that, rarely why; they hide individual requests |
| Traces | The path of one request across services, with the time spent in each step | Show where a request's time went and which hop failed | Usually sampled; the request you need may not have been kept |
| Logs | Individual events with details | Explain what exactly happened in one place | Volume; hard to aggregate; only as good as what was written |
| Change events | Deploys, config changes, feature-flag flips, migrations, scaling events | Answer "what changed?" (Chapter 1) | Only useful if they are recorded and shown on the same timeline |
The fourth row is the one teams forget. Most incidents are caused by a change, and the single most useful thing on a dashboard is a deploy marker — a vertical line at the moment each release went out.
The investigation moves from aggregate to specific: metrics to see the shape of the problem, traces to find where it lives, logs to see exactly what happened.
Part 2: Metrics Literacy — How Not to Be Fooled
The metric types
| Type | What it is | Example |
|---|---|---|
| Counter | A number that only goes up | Total requests, total errors. You almost always look at its rate (per second) |
| Gauge | A value that goes up and down | Memory used, pool connections in use, queue length |
| Histogram | Counts of values in buckets, so percentiles can be computed | Request duration |
Averages lie; percentiles tell the truth
Suppose 99 requests take 50 ms and one takes 10 seconds. The average is about 150 ms — it looks fine, and describes no request that actually happened. The percentiles describe the experience:
- p50 (the median) — half of requests are faster than this. The typical experience.
- p95 — 95% are faster. The experience of a meaningful minority.
- p99 — 99% are faster. The tail — your slowest regular users, often your biggest customers with the most data.
Watch p50 and p99 together. p50 rising means everyone got slower. p99 rising while p50 stays flat means some requests got much slower — usually one endpoint, one customer, one dependency or one host.
Two percentile traps
- You cannot average percentiles. The p99 across ten servers is not the average of the ten servers' p99s. Correct percentiles come from combining the underlying histograms.
- Tail latency multiplies. If a page calls 10 services in parallel and waits for all of them, and each is slow 1% of the time, the page is slow whenever any of them is: 1 − 0.99¹⁰ ≈ 9.6% of page loads. A tiny tail in each service becomes a large tail for users.
Resolution hides spikes
A graph that shows one point per minute, averaged, smooths a 5-second total outage into a small bump. When the shape matters, zoom in to the finest resolution available, and prefer max or high percentiles over averages for spiky problems.
Cardinality
Each distinct combination of labels on a metric is a separate time series. Labelling a request metric with route and status is fine — there are a few hundred combinations. Labelling it with user_id creates millions, which is slow, expensive, and often rejected by the metrics system. Per-user detail belongs in logs and traces, not metric labels.
Part 3: What to Watch — RED, USE and Saturation
Two short checklists cover almost everything.
RED — for every service or endpoint:
- Rate — requests per second.
- Errors — failed requests per second (or the error percentage).
- Duration — latency percentiles.
USE — for every resource (CPU, memory, disk, connection pool, thread pool, queue):
- Utilisation — how busy it is (CPU at 70%, 18 of 20 pool connections in use).
- Saturation — how much work is waiting for it (requests waiting for a connection, run-queue length, queue depth, event-loop delay).
- Errors — errors from the resource itself (pool timeouts, disk errors).
Saturation is the early warning. Utilisation can sit at 100% while things are fine. The moment work starts waiting — pool waitingCount above zero (Chapter 9), event-loop delay rising (Chapter 8), queue depth climbing — latency is about to rise. Many outages are visible in a saturation metric minutes before they are visible in error rates.
Part 4: SLOs and Alerts That Mean Something
- An SLI (service level indicator) is a measurement of what users experience: "the fraction of checkout requests that succeed in under 1 second."
- An SLO (service level objective) is the target for it: "99.9% over 30 days."
- The error budget is what the SLO allows to fail: 0.1% of requests over 30 days. Spending it fast is an emergency; spending it slowly is normal.
Alert on symptoms, not causes. Page someone when users are hurting — the SLI is bad, or the error budget is burning fast. Do not page someone because CPU is at 85%; if users are fine, CPU at 85% is a dashboard note, not a 3 a.m. phone call. Cause-based alerts (CPU, memory, disk) are useful as warnings and as clues once an investigation starts.
Too many alerts that do not need action lead to alert fatigue (Chapter 7) — people stop trusting pages, and the real one gets ignored.
Part 5: Reading a Trace
A trace is the full journey of one request. It is made of spans — one span per unit of work (an incoming request, a database query, an outgoing HTTP call) — each with a start time, a duration, a parent, a status and attributes. The tracing UI draws them as a waterfall.
POST /checkout ████████████████████████████████████ 2,340 ms
├─ auth.verify █ 18 ms
├─ db: SELECT cart █ 12 ms
├─ db: SELECT product (×42) ▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌ 420 ms
├─ pricing.quote ███ 160 ms
├─ (gap) ░░░░░░░░ 610 ms
└─ payments.charge ████ 1,080 ms
What to read in a waterfall:
| Pattern | What it means |
|---|---|
| The critical path — the chain of spans that determines total duration | Speeding up anything off the critical path does nothing for this request |
Many identical short spans in sequence (SELECT product ×42) | N+1 queries — one per item, one after another |
| Spans that could overlap but run one after another | Sequential awaits on independent work — could run in parallel |
| A gap with no span | Time spent in work that is not instrumented: CPU-heavy code, a blocked event loop (Chapter 8), waiting for a connection from a pool (Chapter 9), or a queue |
| One very long leaf span | The slow dependency — a query, a third-party API |
| A span with error status | Where the failure happened; its attributes often hold the error message |
Sampling — the trace you need may not exist
Recording every trace is expensive, so systems sample:
- Head sampling decides at the start of the request (for example, keep 5%). Cheap, but it decides before anyone knows whether the request will be slow or fail — so 95% of your errors are thrown away.
- Tail sampling decides after the request finishes, so it can keep all errors and all slow requests plus a sample of normal ones. Much better for debugging, more complex to run.
If you cannot find a trace for the failing request, check the sampling before concluding it did not happen.
Exemplars — from a spike to an example
Many metrics systems can attach exemplars to a histogram: trace IDs of example requests in each latency bucket. On a latency graph, you click the spike and jump straight to a trace of a request that was actually that slow. This is the fastest bridge from "something is slow" to "this is why."
Part 6: From Alert to Root Cause — the Workflow
This is the method from Chapter 1, with the instruments from Part 2 plugged in.
Step 4 is where most of the narrowing happens. Break the failing metric down by every dimension you have, one at a time:
| If the problem is limited to… | Suspect |
|---|---|
| One version (the canary, the newest pods) | The deploy — roll it back |
| One host or pod | That machine: a bad disk, a noisy neighbour, a stuck process, a bad config file |
| One availability zone or region | Infrastructure or networking in that zone |
| One endpoint | That code path or its specific dependency |
| One tenant or customer | Their data: size, shape, a malformed record, or their traffic |
| One client (app version, browser, partner) | What that client sends |
| Nothing — it is everywhere at once | A shared dependency: the database, DNS, the auth service, the cache, a third-party API, the network |
"It's everywhere" is as informative as "it's only here." Many services failing at the same moment almost always share one dependency.
Part 7: How Observability Lies
Missing deploy markers. Without change events on the graph, the most likely cause is invisible.
Clock skew. Spans and logs from different machines with slightly different clocks can appear out of order, or a child span can seem to start before its parent. Order within a trace by the parent-child relationships, not by raw timestamps.
Survivorship in latency metrics. If a request times out at the load balancer, your app may never finish it — so it never records a duration. The app's latency graph can look fine while users get 504s. Compare app-side metrics with load-balancer or edge metrics, which see every request.
Sampling dropped the evidence. See Part 5.
Monitoring that shares the failure. If your monitoring runs in the same cluster or depends on the same database as the system it watches, it can go dark exactly when you need it. Keep at least one external check — a simple probe from outside your infrastructure.
Dashboards built for the last incident. A dashboard shows what someone decided to graph. When the current problem is new, go to the raw signals and break them down yourself (Part 6, step 4).
The Worked Investigation — Step Through It
Here is a full investigation as a timeline. At each pause, decide what you would check next before revealing it.
Notice how few things were actually examined. Every check was chosen to cut the space in half: tail vs everyone, one endpoint vs all, deploy vs not, one host vs all, one tenant vs all. Four checks went from "the API is slow" to one query on one tenant's data.
The Causal Chains
- If p99 rises while p50 stays flat
- If p50 rises along with p99
- If you average p99 values from several servers
- If a page waits on many services in parallel
- If saturation rises (pool waiting, queue depth, event-loop delay)
- If the problem appears only on the newest version
- If the problem appears only on one host
- If many services fail at the same moment
- If a trace has a large gap with no spans
- If a trace shows dozens of identical short query spans in sequence
- If you cannot find a trace for a failing request
- If the app's latency graph looks normal while users get 504s
The Rule You Keep Forever
Go from aggregate to specific: metrics show that something is wrong and when, change events show what changed, breakdowns by dimension show where, a trace shows which hop, and logs show exactly what happened. Every step should cut the space of possible causes in half.
Vocabulary Map
| Term | What it means |
|---|---|
| Observability | Being able to answer new questions about a system from the signals it emits |
| Telemetry | The data a system emits about itself: metrics, logs, traces |
| Counter / gauge / histogram | A value that only rises / goes up and down / counts values in buckets |
| Rate | Change of a counter per second |
| Percentile (p50, p95, p99) | The value below which that percentage of measurements fall |
| Tail latency | The slowest requests — the high percentiles |
| Cardinality | The number of distinct label combinations, i.e. separate time series |
| RED | Rate, Errors, Duration — the service checklist |
| USE | Utilisation, Saturation, Errors — the resource checklist |
| Saturation | How much work is waiting for a resource — the leading indicator |
| SLI / SLO | A measure of user experience / the target for it |
| Error budget | The amount of failure the SLO allows |
| Burn rate | How fast the error budget is being used up |
| Symptom-based alert | An alert on user impact rather than on a cause like CPU |
| Deploy marker | A line on a graph showing when a release went out |
| Trace / span | One request's full journey / one unit of work within it |
| Waterfall | The visual layout of a trace's spans over time |
| Critical path | The chain of spans that determines a request's total duration |
| Head / tail sampling | Deciding to keep a trace at its start / after it completes |
| Exemplar | A trace ID attached to a metric data point, linking a spike to an example request |
| Dimension / breakdown | A label (endpoint, host, version, tenant) used to split a metric |
| Canary | A small portion of traffic sent to a new version first |
| Synthetic check / probe | An external test request sent regularly to verify the system works |
Practice Problems
Problem 1 — Read the shape. Error rate is normal, p50 latency doubled from 80 ms to 160 ms, and p99 is unchanged. What kind of cause does this suggest, and what is the first thing you check?
Problem 2 — The averaged dashboard. A dashboard shows "average p99 across hosts" at 300 ms. One host's p99 is 4 s and the other nine are at 100 ms. Explain why the dashboard number is misleading, and how the real p99 should be computed.
Problem 3 — Alert design. Your team pages on-call when any server's CPU exceeds 80% for 5 minutes. On-call is paged three times a week, and users were unaffected each time. Redesign the alerts: what should page, and what should only warn?
Problem 4 — Read the trace. A trace of a 1.8 s request shows: auth 20 ms, a 900 ms gap with no spans, then three sequential HTTP calls of 250 ms each. Give a hypothesis for the gap, a way to confirm it, and a change that would cut the total time without making any single call faster.
Problem 5 — Everywhere at once. At 10:42, error rates rise on six unrelated services simultaneously, with no deploys in the last two hours. Walk through your first five checks in order.
What's Coming Next
That completes Part 2: an instrument for every layer, and a workflow that connects them. Part 3 puts everything together in the Casebook — complete investigations of the bugs engineers meet again and again. Chapter 11 starts with the family behind "it works on my machine": configuration and build differences, time zones, sessions across instances, version skew after deploys, and CORS that breaks only in production.