TL;DR

1. The problem: why do we need observability?

It's 9 p.m. in Brazil, peak time. Some users report that the Instagram feed takes ten seconds to load. Somewhere among the load balancers, the API Gateway, the News Feed Service, ranking, Redis feed caches, Cassandra, the user DB, the CDN and thousands of servers in several regions, something is wrong. Questions you must answer in minutes:

In a monolith on one server you'd SSH in and look at the logs. In a distributed system with hundreds of services, a single user request touches dozens of machines; no human can inspect them one by one. You need the system to emit telemetry that is collected, stored and queryable centrally. That is what this lesson is about.

🧠 Analogy A car's dashboard (speed, fuel, warning lights) is monitoring: a fixed set of gauges for problems the designers anticipated. When a strange noise appears that no warning light covers, the mechanic plugs in a diagnostic computer, reads detailed sensor logs, and follows the problem through the engine — that's observability: rich enough data to investigate problems nobody predicted. A hospital adds the rest: vital-sign thresholds that page a nurse (alerting), on-call doctors (on-call), and case reviews after something goes wrong where the goal is to fix the process, not to blame the nurse (blameless postmortems).

2. Monitoring vs observability

MonitoringObservability
Question"Is the system working? Is a known bad thing happening?""Why is it behaving this way?" — including for failures nobody predicted.
ApproachPredefined metrics, dashboards and threshold alerts.High-cardinality, richly-attributed telemetry you can slice arbitrarily (by user, app version, shard, region, endpoint…).
HandlesKnown-unknowns ("disk might fill up").Unknown-unknowns ("Android 312.0 users on one carrier in São Paulo get slow feeds only for posts with carousels").
RelationMonitoring is a subset of observability. You need both: monitoring to detect, observability to diagnose.

3. The pillars of telemetry

📈 Metrics

Numeric measurements aggregated over time: request rate, error count, p99 latency, CPU %. Tiny and cheap per data point; great for dashboards and alerts; lose per-request detail.

📜 Logs

Timestamped records of discrete events with context: "user 42 upload failed: S3 timeout". Rich detail; expensive at volume.

🧵 Traces

The path of one request across services as a tree of timed spans. Show where time went and which dependency failed.

🔥 Profiles

Where CPU time and memory go inside a process, down to functions (flame graphs). Continuous profiling samples production all the time at low overhead.

🏷️ Events / changes

Deploys, config/feature-flag changes, schema migrations, failovers. Most incidents are caused by a change — overlaying change events on graphs is often the fastest diagnosis.

MetricsLogsTraces
AnswersIs something wrong? How much? Trend?What exactly happened in this event?Where in the request path is the problem?
Cost driverNumber of unique time series (cardinality)Bytes ingested and retainedNumber of spans kept (sampling rate)
Typical retentionMonths–years (downsampled)Days–weeks hot, longer in cold storageDays
Typical toolsPrometheus, Datadog, Meta's ODS/GorillaELK/OpenSearch, Loki, SplunkJaeger, Zipkin, Tempo, Meta's Canopy

The pillars are most powerful when linked: a latency spike on a dashboard → click through to example traces from that minute (via exemplars) → a slow span → the logs for that span (same trace_id) → the profile of the slow host.

ServicesOpenTelemetry SDK Mobile appsperf + crash reports Hosts / infranode exporter, kernel Collectorsagents, batching,sampling, Kafka Metrics TSDBPrometheus / Mimir Log storeOpenSearch / Loki / S3 Trace storeJaeger / Tempo DashboardsGrafana: query + link Alerting → on-callAlertmanager, PagerDuty
A telemetry pipeline: services, apps and hosts emit metrics, logs and traces; collectors batch and sample them; specialised stores keep each type; dashboards query them together and alert rules page the on-call engineer.

4. Metrics in depth

4.1 Metric types

TypeWhat it isInstagram example
CounterMonotonically increasing count; you graph its rate.feed_requests_total{status="500"}
GaugeA value that goes up and down; sampled.Kafka consumer lag, Redis memory used, open WebSocket connections in the DM service.
HistogramCounts of observations in buckets (≤10 ms, ≤25 ms, ≤50 ms…); percentiles computed at query time; aggregatable across hosts.feed_request_duration_seconds_bucket
SummaryPercentiles computed in the client process.Rarely preferred — per-host percentiles cannot be averaged into a fleet percentile.

4.2 Labels and cardinality

Metrics carry labels (dimensions): http_requests_total{service="feed", endpoint="/v1/feed/timeline", status="200", region="eu"}. Each unique combination of label values is a separate time series stored by the database. Cardinality multiplies: 50 endpoints × 10 status codes × 5 regions × 2,000 hosts = 5M series. Never put unbounded values like user_id, post_id or raw URLs in metric labels — that would create billions of series and take down the metrics system. High-cardinality questions ("which user?") belong in logs and traces.

4.3 Pull vs push

Prometheus pulls ("scrapes") a /metrics endpoint on each target every 15–30 s; a target that disappears is immediately noticed (up == 0). Push systems (StatsD, OpenTelemetry OTLP, Datadog agents) have processes send metrics to a collector — better for short-lived jobs and for crossing network boundaries. Both are widely used; large fleets often combine local agents with a central, horizontally scalable TSDB.

# What a service exposes on /metrics (Prometheus text format)
# TYPE feed_requests_total counter
feed_requests_total{endpoint="timeline",status="200"} 18233412
feed_requests_total{endpoint="timeline",status="503"} 1204
# TYPE feed_request_duration_seconds histogram
feed_request_duration_seconds_bucket{endpoint="timeline",le="0.1"}  15011290
feed_request_duration_seconds_bucket{endpoint="timeline",le="0.25"} 17950003
feed_request_duration_seconds_bucket{endpoint="timeline",le="0.5"}  18190117
feed_request_duration_seconds_bucket{endpoint="timeline",le="+Inf"} 18234616
feed_request_duration_seconds_sum{endpoint="timeline"}   2735192.4
feed_request_duration_seconds_count{endpoint="timeline"} 18234616

5. What to measure: golden signals, RED and USE

5.1 The four golden signals

Google's SRE book proposes that if you can only measure four things about a user-facing system, measure these:

SignalMeaningFeed Service example
LatencyTime to serve a request — track successful and failed requests separately (a fast error is not a fast success).p50/p95/p99 of GET /feed/timeline
TrafficDemand on the system.Feed requests/s per region and app version
ErrorsRate of failed requests — explicit (5xx), implicit (200 with an empty feed), or policy (slower than 1 s counts as failure).% of feed loads returning errors or empty
SaturationHow "full" the service is — the most constrained resource; often a leading indicator.Worker-thread utilisation, queue depth, Redis CPU, connection-pool usage

5.2 RED — for request-driven services

Rate (requests/s), Errors (failed requests/s), Duration (latency distribution). Every microservice — Upload, Feed, Search, Like & Comment — gets the same three graphs per endpoint, which makes any service's dashboard instantly readable by any engineer.

5.3 USE — for resources

For every resource (CPU, memory, disk, network interface, connection pool, thread pool): Utilisation (% of time busy / % capacity used), Saturation (extra work queued that can't be served — run-queue length, swap, dropped packets), Errors (disk errors, NIC errors, OOM kills). USE finds the bottleneck; RED tells you whether users are affected.

💡 Instagram-specific signals Beyond generic RED/USE, each component has its own critical signals: Kafka consumer lag for fan-out, search-indexing and notification workers (are we falling behind?); replication lag on PostgreSQL replicas; cache hit ratio for Redis feed caches and the CDN; push delivery rate via APNs/FCM; media-processing queue age; WebSocket connection count for DMs; and client-side metrics such as app cold-start time, feed time-to-first-image and crash-free sessions.

6. Percentiles and histograms: why averages lie

A percentile pN is the value below which N% of observations fall: p50 is the median; p99 means "99% of requests were faster than this". Averages hide what users actually experience:

100 feed requests: 98 take 50 ms, 2 take 5,000 ms (stuck on a slow shard)
  average = (98×50 + 2×5000) / 100 = 149 ms        ← "looks fine"
  p50     = 50 ms
  p99     = 5,000 ms                                ← 1 in 50 users waits 5 seconds

Bimodal cache: 80% hits at 5 ms, 20% misses at 200 ms
  average = 0.8×5 + 0.2×200 = 44 ms                 ← no request ever took 44 ms!

Why the tail matters even more at Instagram's scale:

6.1 Computing percentiles correctly

You can't store every latency of 100,000 req/s forever, and you cannot average percentiles: the p99 of host A averaged with the p99 of host B is not the fleet p99. The solution is the histogram: each host counts requests into fixed buckets; bucket counts can be summed across hosts and time; the fleet percentile is estimated from the merged buckets.

latency bucket   ≤10ms  ≤25ms  ≤50ms  ≤100ms  ≤250ms  ≤500ms  ≤1s   ≤2.5s  +Inf
host A counts     120    800   2400    600      60      15     4      1      0
host B counts      90    700   2600    500      80      20     8      2      0
sum (fleet)       210   1500   5000   1100     140      35    12      3      0   total 8000
cumulative        210   1710   6710   7810    7950    7985  7997   8000
p99 → rank 7920 falls in the ≤250ms bucket (7810 < 7920 ≤ 7950) → p99 ≈ 100–250 ms (interpolate ≈ 220 ms)

Accuracy depends on bucket boundaries — choose them around your SLO thresholds. Libraries such as HdrHistogram, DDSketch and Prometheus native histograms use exponential buckets to keep relative error small (~1–2%) at any scale.

7. SLIs, SLOs, SLAs and error budgets

TermDefinitionExample for the Instagram feed
SLI — Service Level IndicatorA measured quantity that reflects user experience, usually a ratio of good events to total events.Fraction of feed requests that return successfully and within 800 ms, measured at the load balancer.
SLO — Service Level ObjectiveAn internal target for the SLI over a window.99.9% of feed requests good over a rolling 28 days.
SLA — Service Level AgreementA contract with customers with consequences (refunds/credits) if missed. Looser than the SLO.Consumer Instagram has no SLA; a paid API product (e.g. a business messaging API) might promise 99.5%.

7.1 Error budgets — worked example

An SLO of 99.9% means you are allowed 0.1% bad requests — that allowance is the error budget. Assume 10 billion feed requests per day:

28-day window: 10B × 28 = 280B requests
Error budget  = 0.1% × 280B = 280M bad requests per 28 days
In time terms = 0.001 × 28 days × 1,440 min = ~40 minutes of total outage per 28 days

Incident: a bad deploy makes 30% of feed requests fail for 20 minutes
  requests in 20 min = 10B / 1,440 × 20 ≈ 139M
  bad requests       = 30% × 139M ≈ 42M  → 15% of the 28-day budget burned

The budget turns reliability into an engineering decision instead of an argument:

7.2 Burn-rate alerting

The burn rate is how fast you're consuming budget relative to plan: burn rate 1 = you'll use exactly 100% of the budget by the end of the window; burn rate 14.4 sustained for 1 hour consumes 2% of a 30-day budget. A widely used scheme (from Google's SRE workbook) uses multiple windows:

ConditionBudget consumedAction
Burn rate ≥ 14.4 over 1 h (and over last 5 min)2% in an hour🚨 Page immediately
Burn rate ≥ 6 over 6 h (and over last 30 min)5% in 6 hours🚨 Page
Burn rate ≥ 1 over 3 days10% in 3 days🎫 Ticket — fix during work hours

The short confirmation window makes the alert stop quickly once the problem is fixed; the long window prevents paging on a brief blip.

8. Logging

8.1 Structured logging

Unstructured logs ("Upload failed for user 42 after 3 retries") are readable by humans but painful to search and aggregate. Structured logs are key-value records (usually JSON) that machines can index and filter:

{
  "ts": "2026-10-23T15:42:07.118Z",
  "level": "ERROR",
  "service": "upload-service",
  "host": "upload-7f9c-eu-3",
  "region": "eu",
  "version": "2026.10.23-1",
  "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736",
  "span_id": "00f067aa0ba902b7",
  "user_id": 184467440737,
  "event": "s3_put_failed",
  "media_id": "3192…",
  "attempt": 3,
  "error": "TimeoutError: PUT exceeded 2000 ms",
  "duration_ms": 2004
}

8.2 Log pipelines

Servicewrites JSON to stdout
→
AgentFluent Bit / Vector
→buffer
Kafkaabsorbs spikes
→parse, enrich, redact
Hot indexOpenSearch, 7–14 days
→age out
Cold archiveS3 / lake, months

The buffer (Kafka) matters: during an incident, error logs spike 10–100×, exactly when you need the log system most. Without a buffer, the log store falls over or the agents drop logs. Cost controls: sample repetitive INFO/DEBUG logs (keep 1%, keep 100% of errors), set per-service ingestion quotas, keep only a few days in the expensive full-text index and the rest in cheap object storage queried on demand. Rough scale (assumption): 20,000 servers × 500 lines/s × 500 bytes ≈ 5 GB/s ≈ 430 TB/day before sampling — logs can easily cost more than the service itself if unmanaged.

9. Distributed tracing

Metrics say the feed is slow; logs show errors in several services. But which service made this request slow? A distributed trace records the full journey of one request through every service as a tree of timed operations.

9.1 Traces, spans and IDs

9.2 An Instagram feed request, traced

trace_id 4bf92f3577b34da6a3ce929d0e0e4736   GET /v1/feed/timeline   user 1844…   total 412 ms
time (ms)        0        50       100      150      200      250      300      350      400
                 |--------|--------|--------|--------|--------|--------|--------|--------|
lb + gateway     [████████████████████████████████████████████████████████████████████████] 412
 ├ auth check    [██] 12
 └ feed-service     [█████████████████████████████████████████████████████████████████████] 395
    ├ redis: timeline  [█] 6                                  (precomputed post IDs)
    ├ counters svc       [████] 20          ┐
    ├ user svc           [███] 18           ├ run in parallel
    ├ stories tray       [████] 22          ┘
    ├ ranking svc        [██████████████████████████████████████████████] 250   ◄ slowest child
    │  ├ feature store     [████████████████████████████████] 180            ◄ ROOT CAUSE
    │  │   attrs: shard=17, keys=480, cache_hit=0.31 (normally 0.95)
    │  └ model inference                                    [████████] 55
    └ hydrate + sign CDN URLs                                            [█████] 30

Reading it: the gateway and auth are fine; the parallel calls are fast; the ranking call dominates, and within it the feature-store lookup takes 180 ms because its cache hit rate on shard 17 dropped from 95% to 31%. Without tracing, the ranking team, the feed team and the feature-store team would each see "slow" in their own metrics and argue about whose fault it is. With tracing, the critical path is obvious in seconds.

9.3 Context propagation

For spans from different services to join one trace, the trace context must travel with the request. The W3C Trace Context standard defines an HTTP header:

traceparent: 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01
             │  │                                │                └ flags (01 = sampled)
             │  │                                └ parent span ID (the caller's span)
             │  └ trace ID (same for the whole request)
             └ version

9.4 Sampling

Recording every span of every request is too expensive (a feed request might produce 50+ spans; at 100k req/s that's 5M spans/s). So traces are sampled:

Head-based samplingTail-based sampling
Decision madeAt the start of the request (e.g. keep 1%), propagated in the traceparent flags so all services agree.After the trace completes, by a collector that buffers all spans briefly.
ProsCheap and simple; no buffering.Can keep the interesting traces: all errors, all slow (> p99) requests, plus a small random baseline.
ConsRare errors are mostly discarded (99% of them with 1% sampling).Needs to collect every span temporarily — expensive infrastructure.

Practical systems combine them: head-sample a baseline, always keep errors and very slow requests, and allow forced tracing for specific debugging (e.g. an engineer's own account or a flagged request).

10. Alerting

10.1 Symptom-based vs cause-based

Alert on symptoms users feel ("feed error rate above SLO burn threshold", "DM delivery p99 > 5 s"), not on every possible cause ("CPU > 80% on host 1234", "one Cassandra node down"). Causes are many and often harmless (a host at 85% CPU serving fine; a replica down with others healthy); symptoms are few and always matter. Cause metrics belong on dashboards for diagnosis. Exceptions: causes that predict imminent user impact without a symptom yet — "disk will be full in 4 hours", "certificate expires in 7 days", "Kafka consumer lag growing for 30 min".

10.2 Pages vs tickets

Page 🚨Ticket 🎫
WhenUsers are hurt now or will be within hours; needs a human immediately.Needs attention, but within a business day is fine.
ExamplesFast error-budget burn, upload success < 99%, region unreachable, notification pipeline stalled.Slow budget burn, a single host's disk degrading, backup job failed once, certificate expiring in 3 weeks.
DeliveryPhone call / push via PagerDuty or Opsgenie, escalates if unacknowledged.Queue in the team's issue tracker.

10.3 Runbooks

Every alert should link to a runbook: what the alert means, the likely causes, the dashboards and queries to check, safe mitigation steps (roll back the last deploy, drain a region, enable degraded mode, flip a kill switch), and who to escalate to. A tired engineer at 4 a.m. should not have to reverse-engineer an alert.

# Prometheus alert rule: fast burn of the feed availability SLO (99.9%)
- alert: FeedSLOFastBurn
  expr: |
    (sum(rate(feed_requests_total{status=~"5.."}[1h])) / sum(rate(feed_requests_total[1h])))  > (14.4 * 0.001)
    and
    (sum(rate(feed_requests_total{status=~"5.."}[5m])) / sum(rate(feed_requests_total[5m]))) > (14.4 * 0.001)
  labels:
    severity: page
    team: feed
  annotations:
    summary: "Feed is burning its error budget 14x too fast"
    runbook: "wiki/runbooks/feed-slo-fast-burn"
    dashboard: "grafana/d/feed-overview"

10.4 Alert fatigue

When engineers receive dozens of alerts per shift, most of them unactionable or duplicates, they start ignoring them — and miss the real one. This is alert fatigue. Remedies:

11. Dashboards

Dashboards are for humans to understand state quickly. Good practice:

12. On-call, incident management and postmortems

12.1 On-call

Each service team runs an on-call rotation: one primary (and a secondary backup) responsible for responding to pages, typically for a week at a time, often "follow-the-sun" across time zones so nobody is woken at night routinely. Healthy on-call has manageable page volume, good runbooks, the authority to roll back, and time after a rough shift to fix the root causes.

12.2 Incident management

  1. Detect: alert fires or users report a problem.
  2. Declare & triage: open an incident, assign a severity (SEV1 = major user-facing outage, e.g. feed down in a region; SEV3 = minor degradation).
  3. Assign roles: Incident Commander (coordinates, decides — doesn't debug), Operations/Tech lead (hands on keyboard), Communications lead (status page, internal updates), Scribe (timeline).
  4. Mitigate first, root-cause later: roll back the deploy, drain the region, turn off the feature flag, enable degraded mode. Restoring users comes before understanding.
  5. Resolve & monitor: confirm metrics are back to normal; keep watching.
  6. Postmortem within days.

12.3 Blameless postmortems

A postmortem is a written analysis of an incident. Blameless means it assumes everyone acted reasonably given the information and tools they had, and asks "how did the system allow this?" rather than "who messed up?". If people are punished for mistakes, they hide them, and the organisation stops learning. A typical template:

SectionExample content
Summary & impact"For 23 minutes, 18% of feed loads in South America failed; ~40M requests affected; 9% of 28-day error budget consumed."
TimelineDetection time, key decisions, mitigation, resolution (from the scribe's notes and telemetry).
Root cause(s) & contributing factorsA config change raised the feature-store cache TTL; a hot shard evicted keys; no canary for config changes; alert fired 9 minutes after impact began.
What went well / badly / luckyRollback was fast; dashboards lacked per-shard breakdown; the issue started off-peak.
Action itemsOwned, prioritised, tracked: "canary config changes per cell", "add per-shard hit-ratio panel", "alert on hit-ratio drop".

13. How Instagram does observability

📱 Appperf, crashes, client errors
→
Edge / LBrequest logs, SLIs
→traceparent
Servicesmetrics, logs, spans
→
CollectionScribe/Kafka-style transport
→
StoresTSDB, logs, traces
📸 Meta's publicly described tooling Instagram runs on Meta's shared infrastructure, and Meta has published papers and posts about several observability systems: ODS (a large-scale time-series metrics store) and Gorilla (an in-memory time-series database with clever compression, VLDB 2015); Scuba (a real-time, in-memory store for slicing raw event samples by any dimension — the "observability" style of investigation); Canopy (end-to-end performance tracing, SOSP 2017); and continuous profiling across the fleet (publicly described as Strobelight). Instagram engineers have also written publicly about profiling and optimising the Python/Django stack (for example, efficiency work around CPython memory and garbage collection). Exact internal setups and numbers aren't public; open-source equivalents are listed below.

Rough numbers (assumptions)

Metrics: 2,000 services × 300 metric names × ~500 label combos ≈ 300M active series
         → requires a horizontally sharded TSDB, aggressive downsampling (10 s → 5 min after 2 weeks)
Traces:  100k feed req/s × 50 spans × 1% head sampling = 50k spans/s (+ all errors and slow traces)
Logs:    ~400 TB/day raw before sampling → keep ~1 week hot, sampled archive in object storage
Error budget for a 99.9% feed SLO at 10B requests/day ≈ 280M bad requests per 28 days

14. Trade-offs & common pitfalls

✅ Benefits

  • Detect problems before (or as soon as) users notice
  • Diagnose in minutes instead of hours (lower MTTR)
  • Objective reliability targets (SLOs) that balance speed and stability
  • Safe continuous deployment via canary analysis
  • Capacity planning and performance optimisation from real data

⚠️ Costs

  • Telemetry storage and processing can rival the cost of the service
  • Instrumentation overhead (CPU, latency, code)
  • Cardinality explosions can take down the metrics system
  • Sampling loses data; logs may leak sensitive information
  • Badly tuned alerts burn out on-call engineers

Common pitfalls

15. Real-world technologies

ToolCategoryWhen to use
Prometheus (+ Thanos, Cortex/Mimir, VictoriaMetrics)Metrics collection, TSDB, PromQL, alert rulesThe open-source default for metrics; add Thanos/Mimir for long-term, multi-cluster scale.
GrafanaDashboards across many data sources; alertingUnified visualisation of metrics, logs and traces.
ELK (Elasticsearch, Logstash, Kibana) / OpenSearchLog ingestion, full-text indexing and searchRich ad-hoc log search; budget for index cost.
Grafana LokiLog aggregation indexing only labelsCheaper logs when full-text indexing isn't needed.
OpenTelemetryVendor-neutral APIs/SDKs and Collector for traces, metrics, logsInstrument once, send anywhere; standard context propagation.
Jaeger, Zipkin, Grafana TempoDistributed tracing backends and UIsStoring and exploring traces (waterfalls, service maps).
Datadog, New Relic, Honeycomb, Dynatrace, SplunkCommercial all-in-one observabilityWhen you'd rather pay than operate the stack; Honeycomb excels at high-cardinality exploration.
Pyroscope / Grafana Pyroscope, ParcaContinuous profilingFinding CPU/memory hot spots in production.
Sentry, Firebase CrashlyticsClient crash and error reportingMobile/web app health.
PagerDuty, Opsgenie, incident.ioPaging, escalation, on-call schedules, incident workflowsTurning alerts into a human response.

16. Interview corner

How would you monitor Instagram's feed service? What would you alert on?

Define user-journey SLIs: fraction of feed loads that succeed and render within, say, 800 ms (measured at the LB and from client telemetry). SLO 99.9% over 28 days. Dashboards: RED per endpoint broken down by region/platform/app version; dependency RED (Redis timeline, ranking, counters, user service); saturation (worker utilisation, queue depth, connection pools); cache hit ratio; fan-out Kafka lag; deploy markers. Alerts: multi-window burn-rate pages on the SLO; tickets for slow burn; a few predictive cause alerts (lag growing, disk full ETA). Every alert has a runbook. Traces with tail-sampling of slow/error requests and structured logs carrying trace IDs for diagnosis.

Why are percentiles better than averages, and how do you compute a fleet-wide p99?

Averages hide the tail and multimodal distributions — 2% of requests at 5 s barely move the mean but ruin the experience for millions of users, and fan-out turns per-call tails into common request-level latency. Percentiles can't be averaged across hosts, so each host records a histogram (bucket counts); you sum bucket counts across hosts and time windows and estimate the p99 from the merged cumulative distribution, with bucket boundaries chosen around the SLO thresholds (or with exponential/sketch histograms like DDSketch for bounded relative error).

Explain SLI, SLO, SLA and error budgets with numbers.

SLI: good requests / total, e.g. feed loads that succeed within 800 ms. SLO: 99.9% over 28 days. With 10B requests/day the budget is 0.1% × 280B = 280M bad requests (≈ 40 minutes of full outage). SLA: an external contract with penalties, set looser than the SLO (e.g. 99.5%), typically for paid APIs. If an incident burns 15% of the budget, that's fine; if the budget is exhausted, risky launches freeze and reliability work takes priority. Alert on burn rate (14.4× over 1 h pages) rather than raw error thresholds.

How does distributed tracing work across services and Kafka?

The first service creates a trace ID and a root span; each outgoing call injects a traceparent header (trace ID, parent span ID, sampled flag); each receiving service extracts it, creates child spans and propagates further. For Kafka, the context is written into message headers, and the consumer starts a span linked to the producer's span. Spans are exported asynchronously to a collector, sampled (head-based baseline plus tail-based keep-all-errors/slow), and assembled into a waterfall in Jaeger/Tempo. Logs include the trace ID for cross-referencing.

What makes a good alert? How do you fight alert fatigue?

Good alerts are symptom-based (user impact), urgent, actionable, owned, and linked to a runbook and dashboard. Fight fatigue by deleting alerts that don't lead to action, converting non-urgent ones to tickets, using burn-rate alerts with confirmation windows, grouping and inhibiting related alerts, and reviewing page statistics every on-call handover with a budget for pages per shift.

Walk through how you'd run an incident and what happens afterwards.

Declare an incident and severity; assign an incident commander, ops lead, comms lead and scribe; open a shared channel and timeline. Focus on mitigation first — roll back recent changes, drain the affected region/cell, enable degraded modes — using dashboards, traces and change logs to localise. Communicate status regularly. After resolution, write a blameless postmortem within days: impact, timeline, root and contributing causes, what went well/badly, and owned action items (e.g. better canaries, new alerts, runbook fixes), tracked to completion.

17. Check your understanding

Your SLO is 99.95% over 30 days for 2B requests/day. How many failed requests is your error budget? How many minutes of complete outage is that?

Requests = 2B × 30 = 60B; budget = 0.05% × 60B = 30M failed requests. Time-wise: 0.0005 × 30 × 1,440 = 21.6 minutes of total outage.

Host A reports p99 = 120 ms and host B reports p99 = 400 ms, with equal traffic. Is the fleet p99 260 ms?

No. Percentiles can't be averaged. The fleet p99 depends on the full distributions: if B's slow requests make up most of the slowest 1% overall, the fleet p99 could be close to 400 ms or above. Merge histogram buckets from both hosts and compute the percentile from the combined distribution.

An engineer adds user_id as a label to the feed latency histogram with 12 buckets. With 500M daily users, what happens?

Each user creates ~14 new series (12 buckets + sum + count) → ~7 billion time series — a cardinality explosion that would overwhelm the metrics backend. Per-user questions belong in logs/traces (high-cardinality stores), or use bounded dimensions (region, app version, platform).

Which should page someone at 3 a.m.: (a) CPU at 90% on one feed host, (b) feed error budget burning at 20× for 10 minutes, (c) a backup job failed once, (d) Kafka fan-out lag growing steadily for 45 minutes?

(b) definitely — active user impact burning budget fast. (d) likely — it predicts imminent impact (new posts not reaching feeds) and won't fix itself. (a) is a cause with no proven user impact → dashboard/ticket. (c) → ticket for the next business day (unless backups have been failing repeatedly).

With 1% head-based trace sampling, an error affects 0.05% of requests at 100k req/s. How many error traces per minute do you capture? How would tail-based sampling change this?

Errors: 0.0005 × 100,000 = 50/s = 3,000/min; with 1% head sampling you keep only ~30 per minute (and possibly fewer if the error affects only certain users). Tail-based sampling that keeps all error traces would capture all ~3,000/min (or a capped subset), making rare errors much easier to diagnose.

In a trace, the feed request takes 900 ms. Spans: Redis 5 ms, counters 30 ms and user service 25 ms (parallel), ranking 820 ms of which feature store 760 ms. Where do you look first, and why not at the counters service even though it logged errors?

The feature store inside ranking — it's on the critical path and accounts for ~85% of the latency. The counters service's errors may be real but it ran in parallel for only 30 ms (and presumably has a fallback), so it isn't what made this request slow. Tracing's value is precisely identifying the critical path.

Why should your monitoring and alerting stack not run only in the same region and on the same Kafka cluster as production?

Because the failures you most need to see — a regional outage or a Kafka failure — would also take down your telemetry, leaving you blind exactly when it matters. Run monitoring out-of-band (another region/provider), use external synthetic probes, and alert on missing data ("no metrics received").

🎯 Key takeaways

Related lessons