InterviewKata
System design · Module 25 of 29 · Level 5: Production and architecture

Observability: seeing inside a running system

A design isn't finished when it's drawn; it's finished when you can tell whether it's working. In production you can't attach a debugger to fifty services. You rely on what the system tells you: logs, metrics and traces. This module covers what each is for, how to follow one request across many services, how to avoid drowning in data, and how to alert on what users feel rather than on every twitch of a CPU graph.

1. The idea

A car's dashboard shows speed, fuel and engine temperature at a glance (metrics). The service history is a detailed record of every visit and what was found (logs). And when a delivery is late, the parcel tracker shows each depot it passed through and how long it waited at each one (a trace). You need all three: the dashboard tells you something's wrong, the tracker tells you where, and the records tell you why.
SignalWhat it isAnswersCost
MetricsNumbers aggregated over time: request rate, error rate, latency percentiles, queue depth, CPU"Is something wrong? Since when? How bad?" Cheap to keep for months; drive dashboards and alertsLow per data point, but grows with every label combination (cardinality)
LogsTimestamped records of events, ideally structured (JSON) with fields like user_id, order_id, trace_id"What exactly happened to this request or user?"High: every line is stored and indexed. Sample or tier the noisy ones
TracesThe path of one request through every service, as a tree of timed spans sharing one trace ID"Where did the time go? Which service failed?"Moderate: usually sampled (keep all errors and slow ones, a fraction of the rest)

Observability means being able to answer new questions about the system's behaviour from these signals, without shipping new code. Monitoring is the subset you watch continuously and alert on.

What to measure: RED and USE

2. How it works

Following one slow request

A user says checkout was slow. Press Play to see how a distributed trace finds the cause.

How traces stay connected

The first service creates a trace ID and passes it, with its own span ID, to every service it calls, in the standard traceparent HTTP header defined by W3C Trace Context (and through message headers for queues). Each service records its spans with that trace ID and its parent. A tracing backend then assembles the tree. Put the same trace ID in every log line, and you can jump from a slow span straight to its logs.

Alerting on what matters: SLO burn rates

"CPU above 80%" alerts wake people for things users never notice, and miss things they do. Better: alert when the system is spending its error budget (Module 04) too fast. The burn rate is how many times faster than "exactly on budget" you're failing: with a 99.9% SLO, a 1% error rate is a burn rate of 10 (1% ÷ 0.1%), which would use a 30-day budget in 3 days.

Google's SRE workbook recommends multi-window, multi-burn-rate alerts, for example: page if the burn rate is above 14.4 over the last hour (2% of the monthly budget gone in an hour) or above 6 over the last 6 hours (5% gone), each confirmed by a short window so the alert also stops quickly; open a ticket for a burn rate of 1 over 3 days. Try it in section 3.

Cardinality: the hidden bill

Each unique combination of a metric's labels is a separate time series. http_requests{service, endpoint, status} with 20 services × 50 endpoints × 10 statuses is 10,000 series: fine. Add user_id with a million users and it's 10 billion: your metrics system falls over. Keep unbounded values (user IDs, order IDs, full URLs) in logs and traces, never in metric labels.

3. Try it yourself

Should this page someone?

2.0%
0.4%

How many time series?

Choose the labels on http_request_duration_seconds (a histogram with 12 buckets per series).

4. Trade-offs

Detail vs cost

Observability bills can rival compute bills. Keep metrics broad and cheap, sample traces (always keep errors and slow ones), and set log levels and retention by value.

Symptoms vs causes

Page on symptoms users feel (SLO burn, error rate, latency). Use cause metrics (CPU, disk, queue depth) on dashboards and for diagnosis, not for waking people.

Head vs tail sampling

Deciding to keep a trace at the start (head) is cheap but misses rare slow requests. Deciding at the end (tail, in a collector) keeps the interesting ones but needs buffering.

Build vs buy

Open-source stacks (Prometheus, Grafana, Loki, Tempo, Jaeger) give control and need operating; vendors (Datadog, New Relic, Honeycomb, Grafana Cloud, cloud-native tools) cost more and run themselves. OpenTelemetry keeps you portable.

5. In real systems

The technologies up close

The default answer in an interviewRED metrics per service, traces with OpenTelemetry across the request path, structured logs with trace IDs, and paging on SLO burn rate for checkout latency and errors. Add client-side monitoring for apps. For a monitoring or logging design, cardinality, cost and the pipeline are the deep dives.

OpenTelemetry

The default way to instrument

One vendor-neutral set of APIs, SDKs and a collector for traces, metrics and logs.

How it works

Libraries auto-instrument common frameworks (HTTP servers, database clients, queues) and propagate the trace context between services. The Collector receives telemetry, samples, enriches and batches it, and exports it to any backend, so switching vendors doesn't mean re-instrumenting.

Status today

A CNCF project supported by every major observability vendor and cloud; profiles are being added as a fourth signal.

Use it when

  • Any new service; especially many services in several languages

In the interview

Services are instrumented with OpenTelemetry; the collector samples traces and sends everything to our backend.

🚩 Red flags
  • Vendor-specific agents everywhere, locking every service to one tool.

Prometheus and Grafana (metrics)

The default metrics stack

Prometheus scrapes numeric time series from services and evaluates alert rules; Grafana draws dashboards over it and other sources.

How it works

Services expose counters, gauges and histograms on an endpoint; Prometheus pulls them every 15–60 s into a local time-series database and evaluates PromQL alert rules; Alertmanager groups, deduplicates and routes alerts. Long-term, horizontally scaled storage comes from Thanos, Mimir or VictoriaMetrics (Design D18).

Status today

The de-facto open-source standard, also offered as managed services by the major clouds.

Use it when

  • RED and USE metrics, SLO dashboards and burn-rate alerts

In the interview

RED metrics per service in Prometheus, SLO burn-rate alerts through Alertmanager.

🚩 Red flags
  • Labels with unbounded values (user ID, URL with IDs): cardinality explodes.

Distributed logging pipeline

Situational · logging-system designs

Getting logs from thousands of machines into one searchable place, affordably.

How it works

  • Structured logs (JSON with trace_id, user_id, level) written to local files or stdout.
  • Agents on each host (Fluent Bit, Vector, the OpenTelemetry Collector) tail, parse and batch them; they buffer locally if the pipeline is down.
  • A buffer (usually Kafka) absorbs spikes and lets several consumers read: the search index, the archive, security analytics.
  • Hot store for recent days (Elasticsearch/OpenSearch, Loki, ClickHouse); cold store in object storage for months or years.
  • Cost control: sample or drop debug logs, keep every error, set retention per log type.

Status today

Common stacks: Fluent Bit or Vector → Kafka → OpenSearch or Loki, with S3 archives; managed alternatives from every observability vendor and cloud.

Use it when

  • "Design a logging system"
  • Any large fleet where SSH-ing into machines doesn't scale

In the interview

Agents ship structured logs to Kafka; an indexer keeps 7 days hot in OpenSearch and archives the rest to S3; debug logs are sampled.

🚩 Red flags
  • Indexing every log line forever: the bill grows faster than traffic.
  • Logs without trace IDs: you can't jump from a slow trace to what it logged.

Distributed tracing backends

Situational · many services

Stores and searches traces: the tree of timed spans one request made across services.

How it works

Trace context travels in the W3C traceparent header; each service sends its spans to the collector. Tail-based sampling keeps every error and slow trace and a fraction of the rest, decided after the whole trace is seen.

Status today

Jaeger, Grafana Tempo, Zipkin and vendor backends; the idea comes from Google's Dapper paper (2010).

Use it when

  • Microservices, where a slow request could be anywhere

In the interview

Tail-sampled traces keep every error and slow request, so we can always find the cause of a bad p99.

🚩 Red flags
  • Head sampling at 1%, then wondering why the one failing request isn't there.

Client-side monitoring: RUM, crash reports, synthetic checks

Situational · what users actually see

Measuring the product from the user's device, because server metrics can look perfect while the app is broken.

How it works

  • Real-user monitoring (RUM): a small script or SDK reports page loads, Core Web Vitals (LCP, INP, CLS), API timings and JavaScript errors from real users, by device, country and release.
  • Crash and error reporting: mobile and web SDKs (Sentry, Firebase Crashlytics) send stack traces with the app version and the user's last actions.
  • Synthetic monitoring: scripted probes from many regions hit key flows (log in, check out) every minute, catching outages even when nobody is using that path.

Status today

Standard practice: Sentry, Crashlytics, Datadog RUM and similar vendors; browsers expose the timing data through standard performance APIs.

Use it when

  • Consumer apps, mobile apps, anything where the client does real work
  • Catching bad releases of the app itself

In the interview

RUM and crash reporting by app version, plus synthetic checkout probes from five regions, alerting on errors per release.

🚩 Red flags
  • Only server-side metrics: a broken JavaScript bundle or a mobile crash loop never shows up as a 500.

When the page fires: incidents, runbooks and reviews

The default · how teams handle outages

Alerts (above) decide when a person is woken. What happens next decides how long users suffer. Teams that recover fast follow the same few habits: one person in charge, stop the damage before finding the cause, and learn from it afterwards without blame.

How it works

Severity sets the response. Agree the levels before you need them:

LevelExample (a food-delivery app)Response
SEV1No one can place ordersPage now, incident lead named, status page within 15 minutes, all hands as needed
SEV2Card payments fail for one bank; courier tracking frozenPage the owning team; status page if customers notice
SEV3Restaurant reports a day lateA ticket for working hours

A runbook turns an alert into actions. Each alert links to one page: what the alert means for users, how to confirm it (the dashboard and the query), safe first actions in order (roll back, turn off the flag, fail over, add capacity), what not to do, and who to call next. A runbook step that is always the same is a candidate for automation.

Design so the 3 a.m. questions have quick answers:

  • What changed? Deploys, flag flips and config pushes recorded as events on the same dashboards as the metrics.
  • Who is affected? Metrics broken down by region, app version and customer tier, within your cardinality budget.
  • What happened to this request? A request ID in every log line and trace, returned to the client so support can quote it.
  • Can we undo it? Every release and every flag can be reversed in minutes, without a new build.

Status today

Most companies run some version of this, often borrowed from emergency services' incident command and described in Google's SRE books; tools such as PagerDuty and incident.io handle paging, roles and timelines.

Use it when

  • Any system with an on-call rotation
  • The interview question "how would you operate this?"

In the interview

Every alert links to a runbook whose first step is usually "roll back or turn off the flag". We mitigate first, find the cause after, and every SEV1 gets a blameless review with owners and dates on the actions.

🚩 Red flags
  • Debugging live while users are still failing when a rollback was available.
  • Reviews that end with "be more careful" instead of a change to the system.

6. Remember it

Memory hook: "Dashboard says something's wrong, tracker says where, records say why"Metrics detect, traces locate, logs explain, and alerts fire on what users feel, measured against the error budget.
How this hook works: what "dashboard, tracker, records" means

What the slogan means, phrase by phrase

"Dashboard says something's wrong"
Metrics (RED, USE, SLO burn) show that and when behaviour changed, cheaply, for everything.
"tracker says where"
A trace shows which service and which call in the request's path took the time or failed.
"records say why"
Structured logs, found by trace ID, give the detail: the error message, the input, the user.

Why this picture fits: a late parcel is investigated the same way, and nobody confuses the dashboard light with the repair record.

The story → the concept

Car and parcelObservability
The dashboard gaugesMetrics
The warning lightAn alert on an SLO burn rate
The parcel's tracking historyA distributed trace
Each depot scanA span
The tracking numberThe trace ID, carried in traceparent
The service historyLogs

Watch it on an example

  1. Dashboard: checkout p99 jumped from 400 ms to 3 s at 14:05; the burn-rate alert pages.
  2. Tracker: slow traces all show a 2.6 s span in the fraud-check call; other spans are normal.
  3. Records: fraud-check logs for those trace IDs show "connection pool exhausted" since a 14:02 config change.
  4. Roll back the change; p99 recovers. Time to cause: minutes, not hours.

The trap it saves you from

Grepping logs on fifty servers during an outage, or alerting on every resource metric until on-call engineers learn to ignore alerts.

It still works when…

You run AI features (Module 29): traces of model and tool calls, metrics for tokens, cost and latency, and logs (carefully redacted) of prompts and outputs.

7. Common mistakes

High-cardinality labels on metricsA user ID or full URL as a label can create millions of series and take down the metrics system. Keep them in logs and traces.
Alerting on causes instead of symptoms"CPU 85%" pages that users don't feel train people to ignore alerts. Page on SLO burn; graph the causes.
Averages instead of percentilesAn average latency hides the slow tail (Module 02). Track p95 and p99, from histograms.
Losing the trace ID across a queueIf the trace context isn't copied into message headers, traces stop at the first asynchronous hop.
Logging secrets and personal dataTokens, passwords and card numbers in logs are a security incident waiting to happen. Redact at the source.

8. Design exercise (25 minutes)

  1. Instrument a design: for the URL shortener or Pastebin, list the RED metrics per service, the spans in a redirect's trace, and the fields every log line should carry.
  2. Write SLO alerts: redirects must succeed 99.95% of the time. Compute the burn-rate thresholds for a 1-hour and a 6-hour window, and check them in the calculator.
  3. Find the cardinality bomb: a team wants orders_total{customer_id, product_id, country}. Estimate the series, and propose a better design.
  4. Plan sampling: 20,000 requests a second, budget for 200 traces a second. What do you keep?
  5. Write it down: RED, USE, and the burn-rate formula.

9. Self-check quiz

Which signal shows which service a slow request spent its time in?

A trace's spans show each step's duration along the request's path.

With a 99.9% SLO, the error rate is 1%. What's the burn rate?

1% ÷ 0.1% (the budgeted error rate) = 10: the budget would last a tenth of its period.

Which should never be a metric label?

Unbounded values create a time series per user. Put them in logs and traces.

What does RED stand for?

The three per-service signals that describe what users experience.

Server dashboards are green, but users report the checkout button does nothing. What would have caught it?

A broken bundle never reaches the server, so only client-side signals see it.

Checkout errors spike 9 minutes after a release. The cause isn't clear yet. What's the first move?

Stopping the damage comes before understanding it; a rollback is minutes and reversible.

10. Go deeper

ResourceTimeWhy
Monitoring Distributed Systems (Google SRE book)40 minThe four golden signals, symptoms vs causes, and what makes a good alert.
Alerting on SLOs (Google SRE workbook)45 minBurn rates and multi-window alerts, worked step by step.
OpenTelemetry concepts45 minSignals, context propagation, the Collector and sampling.
Dapper (Google, 2010)40 minThe paper that introduced large-scale distributed tracing.
The USE Method (Brendan Gregg)15 minA checklist for finding resource bottlenecks.

11. Under the hood (optional)

How metrics, traces and logs are really recorded, sampled and turned into alerts. Not needed for most interviews; open it when you want to know how the products really do it. All the deep dives are listed on the Under the hood page.

Observability internalsMetric types and a percentile lab, cardinality, traceparent propagation lab, head vs tail sampling (animated), logs indexing, SLO burn-rate alerts, profiling and eBPF, 6 products comparedabout 30 min

Dashboards are only as truthful as the data types behind them. How a latency percentile is computed, what a trace ID looks like on the wire, which traces are kept, and what an alert actually measures decide whether you find the problem or a misleading average.

Metric types

TypeWhat it storesUse it forTrap
CounterA number that only goes up (resets on restart)Requests, errors, bytes; graph its rateGraphing the raw value instead of rate()
GaugeA value that goes up and downQueue length, memory, temperatureSampling misses spikes between scrapes
HistogramCounts of observations per bucket (≤ 10 ms, ≤ 25 ms, …), plus sum and countLatency and size distributions; aggregatable across serversPercentiles are estimated within a bucket; choose buckets around your SLO
Native / exponential histogramBuckets on an exponential scale chosen automatically, stored sparselyAccurate percentiles at low cost (Prometheus native histograms, stable since 3.8; OpenTelemetry exponential histograms)Needs opt-in and backend support
SummaryQuantiles computed inside each processA single process's latencyQuantiles can't be combined across instances

Cardinality and cost

Every unique combination of metric name and label values is a separate series stored and indexed forever (until retention). http_requests_total{route, method, status} with 50 routes × 5 methods × 10 statuses is 2,500 series per instance, fine; adding user_id multiplies it by your user count. Put high-cardinality detail in logs and traces, where it's sampled or stored cheaply, and keep metrics for aggregates.

Traces: context propagation

A trace is a tree of spans, one per unit of work, linked by IDs passed from service to service in the W3C traceparent header:

  • Head sampling decides at the first service (say, keep 1%) and passes the decision downstream in the flags, so a trace is kept or dropped as a whole. Cheap, but slow and failed requests are dropped at the same rate as boring ones.
  • Tail sampling buffers all spans of a trace in a collector and decides once it's complete: keep every error and every trace over 2 s, plus 1% of the rest. Better traces, but the collector must hold whole traces in memory and route all spans of a trace to the same instance.
  • Exemplars attach a trace ID to a metric sample, so a spike on a latency graph links straight to an example slow trace.
  • OpenTelemetry provides the SDKs, the wire protocol (OTLP) and the Collector, which receives, processes (batches, samples, scrubs personal data) and exports to any backend.

Logs: index everything or index labels

Index every word (Elasticsearch, OpenSearch, Splunk)Index labels only (Grafana Loki, many cloud log stores)
StorageInverted index on local disks, often larger than the raw logsCompressed chunks in object storage plus a small label index
QueriesFast full-text search anywherePick streams by label, then scan (grep) their chunks in parallel
CostHigh at large volumesMuch lower; slower for needle-in-a-haystack searches

Either way: log structured JSON with a trace ID, a consistent level and no secrets or personal data; sample or drop debug logs in production; and use a pipeline (Fluent Bit, Vector, the OpenTelemetry Collector) to buffer, enrich and route.

Alerting on SLOs: burn rates

An SLO of 99.9% success over 30 days allows 0.1% errors: the error budget. The burn rate is how fast you're spending it: a burn rate of 1 uses exactly the budget in 30 days; 14.4 uses 2% of it in one hour. The Google SRE workbook recommends paging when both a long and a short window agree, so alerts are fast for big incidents but don't fire on brief blips:

SeverityBurn rateLong windowShort windowBudget consumed when it fires
Page14.41 hour5 minutes2%
Page66 hours30 minutes5%
Ticket13 days6 hours10%

Profiles, eBPF and real users

  • Continuous profiling (Grafana Pyroscope, Parca, cloud profilers) samples stacks across the fleet at about 1% overhead, so you can diff CPU and memory by version; OpenTelemetry is adding profiles as a fourth signal.
  • eBPF programs in the Linux kernel observe network calls, latency and CPU for every process without changing code (Cilium Hubble, Pixie, Grafana Beyla), useful for services nobody instrumented.
  • Real-user monitoring measures what browsers and apps actually experienced (Core Web Vitals, crashes), which server metrics can't see.

Product by product

ProductHow it worksThe detail worth knowing
PrometheusPulls metrics from targets, stores series in its TSDB, alerts with PromQL rulesCardinality, not request volume, decides cost; Thanos and Mimir add long-term, multi-cluster storage
Elasticsearch / OpenSearch for logsInverted index over every fieldFull-text search at a storage cost
Grafana LokiLabel index plus compressed chunks in object storageMuch cheaper; queries scan chunks
OpenTelemetrySDKs, OTLP and the Collector for traces, metrics and logsInstrument once, change backends later
Jaeger, Grafana TempoTrace storage; Tempo keeps traces in object storage, found by trace IDCheap retention of every sampled trace
Pyroscope, ParcaContinuous profilingFlame graphs over time and versions

Say it in the interview

Answers that show depth
  • "How do you compute p99 across 50 servers?" From merged histograms (sum the bucket counts, then estimate the quantile), never by averaging each server's p99.
  • "How do you keep tracing affordable?" Tail sampling in the Collector: keep all errors and slow traces, a small share of the rest; store spans in object storage.
  • "When do you page someone?" On fast SLO burn (14.4× over an hour, confirmed over 5 minutes), not on CPU or single errors; slower burns open tickets.

How should you get the p99 latency of a service running on many servers?

Percentiles don't average; the busy servers' requests must be weighted by their count.

What is the advantage of tail sampling over head sampling?

Head sampling decides before anything interesting has happened.

A burn rate of 14.4 sustained for one hour consumes how much of a 30-day error budget?

14.4 × 1 hour ÷ 720 hours ≈ 2%.

Sources