{"id":"observability","title":"Monitoring, logging and tracing","sections":[{"id":"overview","title":"Overview","html":"<p>A system you can't see into is a system you can't run. When checkout gets slow at 2 a.m., someone needs to answer three questions fast: is it actually broken for users, where is it broken, and why. Observability is the practice of building systems so those questions have answers. You emit the right signals, keep them affordable, and alert people only when users are hurting.</p>\n<p>In interviews, monitoring usually shows up near the end (\"how would you know this is working?\") or as a dedicated question like \"design a metrics and alerting system\". A strong answer is specific. It names the few numbers that define health for this product, explains how alerts fire, and shows you know what it costs to collect everything.</p>","headings":[]},{"id":"metrics-logs-and-traces","title":"Metrics, logs and traces","html":"<h3 id=\"metrics-logs-and-traces\">Metrics, logs and traces</h3>\n<p>The three classic signals each answer a different question.</p>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<div class=\"table-wrap\"><table><thead><tr><th>Signal</th><th>What it is</th><th>Question it answers</th><th>Cost profile</th></tr></thead><tbody><tr><td><strong>Metrics</strong></td><td>Numbers aggregated over time: counters, gauges, histograms, each with a few labels</td><td>Is something wrong? How much? Since when?</td><td>Cheap per event, since you store aggregates. Cost grows with the number of distinct label combinations.</td></tr><tr><td><strong>Logs</strong></td><td>One record per event, with whatever detail you choose</td><td>What exactly happened to this request or this user?</td><td>Expensive, since cost grows with traffic.</td></tr><tr><td><strong>Traces</strong></td><td>A tree of timed spans for one request as it crosses services</td><td>Where did the time go, and which hop failed?</td><td>Expensive per request, so usually sampled.</td></tr></tbody></table></div>\n<p>They work as a funnel. A metric tells you the error rate on the payments service jumped at 02:10. A trace of a failing request shows the time going into a call to the fraud-check service. That service's logs, filtered by the trace id, show it timing out against a database replica. Connecting the three, mostly by putting the same trace id in logs and traces, is what turns them from three tools into one investigation.</p>\n<p>Continuous profiling (sampled CPU and memory profiles from production) is increasingly treated as a fourth signal. It answers \"which code is burning the CPU?\", which none of the other three can.</p>\n<figure class=\"diagram\" data-text=\"Services + OTel SDK -> Collector (batch, sample) (OTLP); Collector (batch, sample) -> Metrics store; Collector (batch, sample) -> Log store; Collector (batch, sample) -> Trace store; Metrics store -> Alert rules (SLO burn); Alert rules (SLO burn) -> On-call (page); Dashboards, search -> Metrics store; Dashboards, search -> Log store; Dashboards, search -> Trace store\"><div class=\"diagram-scroll\"><svg class=\"dg\" viewBox=\"0 -10 800 285\" width=\"800\" role=\"img\" aria-label=\"A typical telemetry pipeline. Services emit all three signals through a collector to separate stores\"><defs><marker id=\"dg-arrow\" viewBox=\"0 0 10 10\" refX=\"9\" refY=\"5\" markerWidth=\"7\" markerHeight=\"7\" orient=\"auto-start-reverse\"><path class=\"dg-arrowhead\" d=\"M0,1 L9,5 L0,9 z\"></path></marker></defs><line class=\"dg-edge\" x1=\"151\" y1=\"138\" x2=\"167\" y2=\"138\" marker-end=\"url(#dg-arrow)\"></line><line class=\"dg-edge\" x1=\"288.3\" y1=\"109\" x2=\"343.3\" y2=\"76\" marker-end=\"url(#dg-arrow)\"></line><line class=\"dg-edge\" x1=\"311\" y1=\"138\" x2=\"327\" y2=\"138\" marker-end=\"url(#dg-arrow)\"></line><line class=\"dg-edge\" x1=\"288.3\" y1=\"167\" x2=\"343.3\" y2=\"200\" marker-end=\"url(#dg-arrow)\"></line><line class=\"dg-edge\" x1=\"471\" y1=\"42\" x2=\"487\" y2=\"42\" marker-end=\"url(#dg-arrow)\"></line><line class=\"dg-edge\" x1=\"631\" y1=\"42\" x2=\"647\" y2=\"42\" marker-end=\"url(#dg-arrow)\"></line><line class=\"dg-edge\" x1=\"511.7\" y1=\"109\" x2=\"456.7\" y2=\"76\" marker-end=\"url(#dg-arrow)\"></line><line class=\"dg-edge\" x1=\"489\" y1=\"138\" x2=\"473\" y2=\"138\" marker-end=\"url(#dg-arrow)\"></line><line class=\"dg-edge\" x1=\"511.7\" y1=\"167\" x2=\"456.7\" y2=\"200\" marker-end=\"url(#dg-arrow)\"></line><rect class=\"dg-node dg-box\" x=\"12\" y=\"112\" width=\"136\" height=\"52\" rx=\"8\"></rect><text class=\"dg-label\" text-anchor=\"middle\"><tspan x=\"80\" y=\"134.5\">Services</tspan><tspan x=\"80\" y=\"150.5\">+ OTel SDK</tspan></text><rect class=\"dg-node dg-box\" x=\"172\" y=\"112\" width=\"136\" height=\"52\" rx=\"8\"></rect><text class=\"dg-label\" text-anchor=\"middle\"><tspan x=\"240\" y=\"134.5\">Collector</tspan><tspan x=\"240\" y=\"150.5\">(batch, sample)</tspan></text><path class=\"dg-node dg-store\" d=\"M332,20 a68,7 0 0,1 136,0 v44 a68,7 0 0,1 -136,0 z\"></path><path class=\"dg-line\" d=\"M332,20 a68,7 0 0,0 136,0\"></path><text class=\"dg-label\" text-anchor=\"middle\"><tspan x=\"400\" y=\"49.5\">Metrics store</tspan></text><path class=\"dg-node dg-store\" d=\"M332,116 a68,7 0 0,1 136,0 v44 a68,7 0 0,1 -136,0 z\"></path><path class=\"dg-line\" d=\"M332,116 a68,7 0 0,0 136,0\"></path><text class=\"dg-label\" text-anchor=\"middle\"><tspan x=\"400\" y=\"145.5\">Log store</tspan></text><path class=\"dg-node dg-store\" d=\"M332,212 a68,7 0 0,1 136,0 v44 a68,7 0 0,1 -136,0 z\"></path><path class=\"dg-line\" d=\"M332,212 a68,7 0 0,0 136,0\"></path><text class=\"dg-label\" text-anchor=\"middle\"><tspan x=\"400\" y=\"241.5\">Trace store</tspan></text><rect class=\"dg-node dg-box\" x=\"492\" y=\"16\" width=\"136\" height=\"52\" rx=\"8\"></rect><text class=\"dg-label\" text-anchor=\"middle\"><tspan x=\"560\" y=\"38.5\">Alert rules</tspan><tspan x=\"560\" y=\"54.5\">(SLO burn)</tspan></text><rect class=\"dg-node dg-client\" x=\"652\" y=\"18\" width=\"136\" height=\"48\" rx=\"24\"></rect><text class=\"dg-label\" text-anchor=\"middle\"><tspan x=\"720\" y=\"46.5\">On-call</tspan></text><rect class=\"dg-node dg-box\" x=\"492\" y=\"112\" width=\"136\" height=\"52\" rx=\"8\"></rect><text class=\"dg-label\" text-anchor=\"middle\"><tspan x=\"560\" y=\"134.5\">Dashboards,</tspan><tspan x=\"560\" y=\"150.5\">search</tspan></text><rect class=\"dg-edge-bg\" x=\"141.6\" y=\"90\" width=\"34.8\" height=\"18\" rx=\"4\"></rect><text class=\"dg-edge-label\" x=\"159\" y=\"103\" text-anchor=\"middle\">OTLP</text><rect class=\"dg-edge-bg\" x=\"621.6\" y=\"-6\" width=\"34.8\" height=\"18\" rx=\"4\"></rect><text class=\"dg-edge-label\" x=\"639\" y=\"7\" text-anchor=\"middle\">page</text></svg></div><figcaption>A typical telemetry pipeline. Services emit all three signals through a collector to separate stores</figcaption></figure>","headings":[{"id":"metrics-logs-and-traces","title":"Metrics, logs and traces","depth":3}]},{"id":"what-to-measure-red-use-and-slos","title":"What to measure: RED, USE and SLOs","html":"<h3 id=\"what-to-measure-red-use-and-slos\">What to measure: RED, USE and SLOs</h3>\n<h4 id=\"red-and-use\">RED and USE</h4>\n<p>You can't graph everything usefully, so start from two checklists.</p>\n<p>The <strong>RED method</strong> (from Tom Wilkie) is for anything that serves requests. For every service, track:</p>\n<ul>\n<li><strong>Rate</strong>: requests per second.</li>\n<li><strong>Errors</strong>: failed requests per second, or the fraction that fail.</li>\n<li><strong>Duration</strong>: the latency distribution, as percentiles.</li>\n</ul>\n<p>The <strong>USE method</strong> (from Brendan Gregg) is for resources: CPUs, disks, network links, connection pools, queues. For each one, track:</p>\n<ul>\n<li><strong>Utilization</strong>: how busy it is, as a percentage of time or capacity.</li>\n<li><strong>Saturation</strong>: work waiting that it can't serve yet, such as run-queue length, queue depth or pool waiters.</li>\n<li><strong>Errors</strong>: device or resource errors.</li>\n</ul>\n<p>RED tells you users are affected. USE tells you which resource is the bottleneck. Google's SRE book uses a similar list called the four golden signals: latency, traffic, errors and saturation. For asynchronous pipelines (see <a href=\"/learn/asynchronism#message-queues\">message queues</a>), add queue depth and especially <strong>the age of the oldest unprocessed message</strong>, which shows user-visible delay in a way depth alone doesn't.</p>\n<h4 id=\"slis-slos-and-error-budgets\">SLIs, SLOs and error budgets</h4>\n<ul>\n<li>An <strong>SLI</strong> (service level indicator) is a measured ratio of good events to total events. Example: the fraction of checkout requests that succeed in under 400 ms.</li>\n<li>An <strong>SLO</strong> (objective) is the target for that SLI over a window. Example: 99.9% over 30 days.</li>\n<li>An <strong>SLA</strong> is a contract with consequences if you miss, usually set looser than the SLO.</li>\n</ul>\n<p>The gap between the SLO and 100% is the <strong>error budget</strong>. At 99.9% over 30 days:</p>\n<ul>\n<li>In time: 30 days × 24 h × 60 min = 43,200 minutes, and 0.1% of that is 43.2 minutes of full outage.</li>\n<li>In requests: at 500 million requests a month, 0.1% is 500,000 requests allowed to be slow or fail.</li>\n</ul>\n<p>The budget turns reliability arguments into a number. If the budget is mostly unspent, ship faster and take risks. If it's gone, slow down and fix reliability. It also stops teams from chasing 100%, which no system reaches, and which costs a lot more than users notice. The <a href=\"/learn/availability-patterns#availability-in-numbers\">availability numbers</a> table in the primer shows how quickly each extra nine shrinks the allowed downtime.</p>\n<h4 id=\"percentiles-not-averages\">Percentiles, not averages</h4>\n<p>Averages hide exactly the users you care about. Suppose 100 requests take these times: 95 take 50 ms and 5 take 2,000 ms.</p>\n<ul>\n<li>Mean: (95 × 50 + 5 × 2,000) / 100 = (4,750 + 10,000) / 100 = 147.5 ms. Not a single request took that long.</li>\n<li>Median (p50): 50 ms.</li>\n<li>p99: 2,000 ms. One in twenty users waited 2 seconds, and the mean barely hints at it.</li>\n</ul>\n<p>So report p50, p95 and p99 (and p99.9 for high-volume paths). Two practical rules follow.</p>\n<p>First, <strong>you can't average percentiles.</strong> The p99 of a fleet isn't the mean of each host's p99. Record latency as a histogram with fixed buckets, which you can add up across hosts and then read percentiles from. Prometheus histograms work this way. Precomputed per-host quantiles (Prometheus \"summaries\") can't be combined correctly.</p>\n<p>Second, <strong>tails compound under fan-out.</strong> If a page makes 100 backend calls in parallel and waits for all of them, and each call is slow 1% of the time, the chance that at least one is slow is 1 − 0.99¹⁰⁰ ≈ 1 − 0.366 ≈ 63%. The backend's p99 becomes the page's typical case. That's why per-hop p99 matters even when it looks rare.</p>","headings":[{"id":"what-to-measure-red-use-and-slos","title":"What to measure: RED, USE and SLOs","depth":3},{"id":"red-and-use","title":"RED and USE","depth":4},{"id":"slis-slos-and-error-budgets","title":"SLIs, SLOs and error budgets","depth":4},{"id":"percentiles-not-averages","title":"Percentiles, not averages","depth":4}]},{"id":"alerting-on-symptoms","title":"Alerting on symptoms","html":"<h3 id=\"alerting-on-symptoms\">Alerting on symptoms</h3>\n<p>Every page should mean \"users are hurting, or soon will be, and a human must act now\". Anything else trains people to ignore pages.</p>\n<ul>\n<li><strong>Page on symptoms</strong>: SLO violations, error rates and latency that users feel. Users don't care that CPU is at 90% if requests are fast.</li>\n<li><strong>Use causes for diagnosis</strong>: CPU, memory, disk and replica lag belong on dashboards, or in low-urgency tickets when they predict trouble (a disk will be full in four days).</li>\n<li><strong>Alert on burn rate, not raw thresholds.</strong> Burn rate is how fast you're spending the error budget relative to plan. A burn rate of 1 uses exactly the 30-day budget in 30 days. For a 99.9% SLO, a burn rate of 14.4 means an error rate of 14.4 × 0.1% = 1.44%. Sustained for one hour, it spends 14.4 × 1 h / 720 h = 2% of the monthly budget. That's worth a page. A burn rate of 1 sustained for days is worth a ticket. The Google SRE workbook recommends pairing a long window with a short one (say 1 hour and 5 minutes), so alerts fire fast and also clear quickly once the problem stops.</li>\n<li><strong>Every alert needs an owner and a runbook.</strong> If nobody knows what to do when it fires, it isn't ready to page anyone.</li>\n</ul>","headings":[{"id":"alerting-on-symptoms","title":"Alerting on symptoms","depth":3}]},{"id":"logs-and-traces-in-practice","title":"Logs and traces in practice","html":"<h3 id=\"logs-and-traces-in-practice\">Logs and traces in practice</h3>\n<h4 id=\"structured-logs\">Structured logs</h4>\n<p>Write logs as key-value records, usually JSON, instead of free-form sentences. Then you can filter and aggregate them without fragile regexes.</p>\n<pre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">{</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">\"ts\"</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">:</span><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"2026-03-04T02:10:41.512Z\"</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">,</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">\"level\"</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">:</span><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"error\"</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">,</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">\"service\"</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">:</span><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"payments\"</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">,</span></span>\n<span class=\"line\"><span style=\"color:#005CC5;--shiki-dark:#79B8FF\"> \"msg\"</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">:</span><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"fraud check timed out\"</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">,</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">\"order_id\"</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">:</span><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"o_88213\"</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">,</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">\"timeout_ms\"</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">:</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">800</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">,</span></span>\n<span class=\"line\"><span style=\"color:#005CC5;--shiki-dark:#79B8FF\"> \"trace_id\"</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">:</span><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"7d3f9a12c4e85b60a1f2d3c4b5a69788\"</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">}</span></span></code></pre>\n<p>Always include the trace id. It's the join key between a log line and the request that produced it. Never log secrets, full card numbers or passwords, and treat personal data in logs as personal data, since logs get copied everywhere.</p>\n<h4 id=\"sampling\">Sampling</h4>\n<p>Logs and traces scale with traffic, so volume adds up quickly. Take a service at 20,000 requests per second that writes about 1 KB of logs per request:</p>\n<ul>\n<li>20,000 × 1 KB = 20 MB/s.</li>\n<li>20 MB/s × 86,400 s/day ≈ 1.7 TB per day, before replication and indexing.</li>\n</ul>\n<p>Traces are worse. At 15 spans per request and roughly 500 bytes per span, 20,000 × 15 × 500 B = 150 MB/s, or about 13 TB per day. You sample:</p>\n<ul>\n<li><strong>Head sampling</strong> decides at the start of a request, for example \"keep 1% of traces\", and passes the decision downstream so every service agrees. It's cheap, but it decides before anyone knows whether the request will be interesting.</li>\n<li><strong>Tail sampling</strong> buffers all spans of a trace in a collector until the trace finishes, then keeps it if it was slow or failed, plus a small random share of normal traces. You keep the interesting traces, but the collectors must hold every span briefly, and all spans of a trace must reach the same collector.</li>\n<li>For logs, keep every warning and error, and sample or aggregate routine success lines.</li>\n</ul>\n<h4 id=\"distributed-tracing\">Distributed tracing</h4>\n<p>A <strong>trace</strong> is one request's journey through the system. It's made of <strong>spans</strong>, one per unit of work (an HTTP handler, a database query, a queue publish). Each span records a start time, a duration, a trace id, its own span id and its parent's span id. The parent links let the trace view rebuild the tree and draw the familiar waterfall. Google's Dapper paper described this model, and most tracing systems since have followed it.</p>\n<p>For this to work across services, the context has to travel with the request. The <strong>W3C Trace Context</strong> standard defines a <code>traceparent</code> HTTP header:</p>\n<pre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\"><code><span class=\"line\"><span>traceparent: 00-7d3f9a12c4e85b60a1f2d3c4b5a69788-3e1f0a9b8c7d6e5f-01</span></span></code></pre>\n<p>The four dash-separated fields are the version (<code>00</code>), the trace id (16 bytes as 32 hex characters, shared by every span in the trace), the parent span id (8 bytes as 16 hex characters) and flags (<code>01</code> means sampled). Each service reads the incoming header, creates a child span and sends a new <code>traceparent</code> downstream with its own span id as the parent. A companion <code>tracestate</code> header carries vendor-specific data. For asynchronous hops, put the same context in message headers or metadata, so a trace can follow a request through a queue to the consumer that processes it.</p>\n<p><strong>OpenTelemetry</strong> is the vendor-neutral way to do all of this. It provides APIs and SDKs for most languages, automatic instrumentation for common frameworks, a wire protocol (OTLP) and the Collector, a service that receives, batches, samples and forwards telemetry to whatever backends you use. Instrumenting with OpenTelemetry means you can switch backends without touching application code.</p>","headings":[{"id":"logs-and-traces-in-practice","title":"Logs and traces in practice","depth":3},{"id":"structured-logs","title":"Structured logs","depth":4},{"id":"sampling","title":"Sampling","depth":4},{"id":"distributed-tracing","title":"Distributed tracing","depth":4}]},{"id":"cardinality-and-cost","title":"Cardinality and cost","html":"<h3 id=\"cardinality-and-cost\">Cardinality and cost</h3>\n<p>In a metrics system, every unique combination of metric name and label values is a separate <strong>time series</strong>, with its own storage and index entries. The number of series is the product of each label's distinct values. Take <code>http_requests_total</code> labelled by:</p>\n<ul>\n<li>method (5) × status code (10) × endpoint (50) × instance (200) = 500,000 series.</li>\n</ul>\n<p>That's manageable. Now add a <code>customer_id</code> label with 10,000 customers. In the worst case that's 500,000 × 10,000 = 5 billion series. Even if only a fraction of those combinations ever occur, it's enough to make queries slow, run up a large bill or knock over the metrics backend. The Prometheus documentation warns against exactly this: don't put user ids, email addresses or other unbounded values in labels.</p>\n<p>Rules of thumb:</p>\n<ul>\n<li>Labels should have small, bounded sets of values: region, status class, endpoint template (<code>/orders/{id}</code>, never <code>/orders/88213</code>).</li>\n<li>High-cardinality detail (user id, order id, request id) belongs in logs and trace attributes, where you pay per event, not per series.</li>\n<li>Watch the series count itself as a metric, and set limits per team or per service.</li>\n<li>Retention is a cost lever too. Keep raw data for days, and downsampled rollups for months.</li>\n</ul>","headings":[{"id":"cardinality-and-cost","title":"Cardinality and cost","depth":3}]},{"id":"monitoring-in-an-interview","title":"Monitoring in an interview","html":"<h3 id=\"monitoring-in-an-interview\">Monitoring in an interview</h3>\n<p>You rarely have more than a few minutes for this, so be concrete:</p>\n<ol>\n<li><strong>Name the SLIs for this product.</strong> For a URL shortener: redirect success rate and p99 redirect latency. For a chat app: the share of messages delivered within 1 second. For a payments flow: the checkout success rate. One or two user-centred numbers beat a list of twenty.</li>\n<li><strong>State the SLO and what pages.</strong> \"99.9% of redirects succeed in under 100 ms over 30 days. We page on fast burn of the error budget.\"</li>\n<li><strong>Give each component its dashboard.</strong> RED for every service, USE for databases, caches and queues, plus consumer lag and the age of the oldest message for every async pipeline.</li>\n<li><strong>Mention tracing and correlation.</strong> OpenTelemetry instrumentation, <code>traceparent</code> propagated through HTTP and queue messages, and trace ids in structured logs.</li>\n<li><strong>Show you know the costs.</strong> Sample traces, keep labels low-cardinality, and set retention tiers.</li>\n</ol>\n<p>If the question is \"design a metrics system\" itself, the deep dives are a write-heavy time-series store (append-only, compressed, partitioned by time), the collection model (agents pushing versus a server pulling, as Prometheus does), the rule evaluator that turns queries into alerts, and the cardinality limits that keep it alive.</p>","headings":[{"id":"monitoring-in-an-interview","title":"Monitoring in an interview","depth":3}]},{"id":"check-yourself","title":"Check yourself","html":"<h3 id=\"check-yourself\">Check yourself</h3>\n<details>\n<summary>Your service's mean latency is flat at 80 ms, but support tickets about slowness doubled this week. What could explain it, and what would you look at?</summary>\n<p>The mean hides the tail. A small fraction of requests could have become very slow while most got slightly faster, leaving the mean unchanged. Look at p95, p99 and p99.9 from a latency histogram, broken down by endpoint and region, then pull traces of slow requests to see which hop the time goes to.</p>\n</details>\n<details>\n<summary>An engineer proposes paging on-call whenever any database host's CPU goes above 85%. What's wrong with that, and what would you suggest?</summary>\n<p>CPU is a cause, not a symptom. High CPU might not hurt users at all, and users can be hurting while CPU is fine, so the alert will be noisy and miss real problems. Page on user-facing SLIs such as error rate and latency against an SLO, preferably on error-budget burn rate. Put database CPU on a dashboard for diagnosis, and maybe raise a ticket if it trends toward saturation.</p>\n</details>\n<details>\n<summary>Your SLO is 99.95% of requests succeed over 30 days, and you serve 200 million requests a month. How many failed requests is your error budget, and roughly what error rate would spend it in 3 days?</summary>\n<p>The budget is 0.05% of 200 million: 200,000,000 × 0.0005 = 100,000 failed requests. Spending all of it in 3 days instead of 30 is a burn rate of 10, so the error rate would be about 10 × 0.05% = 0.5%, assuming traffic is evenly spread.</p>\n</details>\n<details>\n<summary>A team adds a `user_id` label to their request-latency histogram so they can debug individual customers. Six hours later the metrics backend is struggling. Why, and how should they get per-user visibility instead?</summary>\n<p>Each distinct user id creates a new series for every histogram bucket and every other label combination, so the series count multiplies by the number of active users. That explodes memory and index size. Remove the label, and record the user id as an attribute on traces and in structured logs instead, where cost scales with events, not with distinct combinations. Then find a user's slow requests by searching logs or traces.</p>\n</details>\n<details>\n<summary>A request goes from the API to an order service, which publishes to a queue, and a worker later charges the card. The trace stops at the queue. What's missing?</summary>\n<p>The trace context isn't propagated across the asynchronous hop. The publisher must write the <code>traceparent</code> (and <code>tracestate</code>) into the message headers, and the worker must extract it and start its span as a child of, or linked to, the publishing span. OpenTelemetry instrumentation for many messaging clients does this, but custom queue code has to do it by hand.</p>\n</details>\n<h3 id=\"further-reading\">Further reading</h3>\n<ul>\n<li><a href=\"https://sre.google/sre-book/monitoring-distributed-systems/\" target=\"_blank\" rel=\"noopener noreferrer\">Google SRE book: Monitoring distributed systems</a>: the four golden signals and symptom-based alerting.</li>\n<li><a href=\"https://sre.google/workbook/alerting-on-slos/\" target=\"_blank\" rel=\"noopener noreferrer\">Google SRE workbook: Alerting on SLOs</a>: burn rates and multiwindow alerts, worked through step by step.</li>\n<li><a href=\"https://www.brendangregg.com/usemethod.html\" target=\"_blank\" rel=\"noopener noreferrer\">Brendan Gregg: The USE method</a>: the original description, with checklists per resource.</li>\n<li><a href=\"https://www.w3.org/TR/trace-context/\" target=\"_blank\" rel=\"noopener noreferrer\">W3C Trace Context</a>: the <code>traceparent</code> and <code>tracestate</code> headers.</li>\n<li><a href=\"https://opentelemetry.io/docs/\" target=\"_blank\" rel=\"noopener noreferrer\">OpenTelemetry documentation</a>: concepts, SDKs and the Collector.</li>\n<li><a href=\"https://prometheus.io/docs/practices/naming/\" target=\"_blank\" rel=\"noopener noreferrer\">Prometheus: Metric and label naming</a>: includes the warning about high-cardinality labels.</li>\n<li><a href=\"https://prometheus.io/docs/practices/histograms/\" target=\"_blank\" rel=\"noopener noreferrer\">Prometheus: Histograms and summaries</a>: why histograms can be aggregated and summaries can't.</li>\n<li><a href=\"https://research.google/pubs/dapper-a-large-scale-distributed-systems-tracing-infrastructure/\" target=\"_blank\" rel=\"noopener noreferrer\">Dapper, a large-scale distributed systems tracing infrastructure</a>: Google's 2010 paper behind modern tracing.</li>\n<li>Alex Xu, <em>System Design Interview – An Insider's Guide, Volume 2</em> (2022), with Sahn Lam, \"Metrics monitoring and alerting system\".</li>\n</ul>","headings":[{"id":"check-yourself","title":"Check yourself","depth":3},{"id":"further-reading","title":"Further reading","depth":3}]}],"sourceFiles":[]}