Observability - Complete Deep Dive

Prerequisites: Performance Metrics, Heartbeat & Health Checks Used in: the last five minutes of every senior interview β€” β€œhow would you know this system is healthy?”


Monitoring vs Observability

Β  Monitoring Observability
Answers Known questions (β€œis CPU high?”) Unknown questions (β€œwhy are Android users in Mumbai slow since 3pm?”)
Built from Predefined dashboards and alerts High-dimensional data you can slice arbitrarily
Failure mode You only see what you thought to measure Cost and cardinality

Say this: β€œMonitoring tells me that something is wrong; observability lets me find out why without shipping new code. The practical test is whether I can answer a question I didn’t anticipate β€” if I have to add a metric and redeploy to debug, I have monitoring, not observability.”


The three pillars

Β  Metrics Logs Traces
What Numeric aggregates over time Discrete timestamped events The path of one request across services
Answers β€œIs it broken? How much?” β€œWhat exactly happened here?” β€œWhere is the time going?”
Cost Cheap (pre-aggregated) Expensive at volume Expensive (usually sampled)
Cardinality Low β€” this is the hard constraint High is fine High is fine
Retention Long (months-years, downsampled) Short (days-weeks) Short, sampled
Tools Prometheus, CloudWatch, Datadog ELK, Loki, CloudWatch Logs Jaeger, Tempo, X-Ray, Honeycomb
Use it to Alert and trend Debug a specific failure Find the slow hop in a distributed call

The workflow that ties them together:

Metric alert fires: "checkout p99 > 1s"
  β†’ dashboard: which service? β†’ payments
  β†’ trace: where in the request? β†’ 800ms in the fraud-check call
  β†’ logs for that trace ID β†’ "fraud model cache miss, falling back to sync scoring"
                            ↑ root cause, in about 90 seconds

This is the answer to β€œhow would you debug a latency regression?” β€” metrics to detect, traces to localize, logs to explain. Naming that sequence is worth more than listing tools.


Metrics

The four golden signals (Google SRE)

Signal Question Typical metric
Latency How slow? p50/p99 histogram, split successful vs failed
Traffic How much? rps, qps, concurrent connections
Errors How often wrong? 5xx rate, exception rate, plus silent failures
Saturation How full? CPU, memory, queue depth, connection pool utilization

⚠️ Split latency by success and failure. A fast-failing 500 looks like a latency improvement if you bucket everything together β€” errors returning in 2ms will pull your p99 down while the system burns.

RED vs USE β€” two lenses, different subjects

Β  RED USE
Applies to Services (request-driven) Resources (CPU, disk, pool, queue)
Measures Rate, Errors, Duration Utilization, Saturation, Errors
Perspective The user’s The machine’s

Say this: β€œRED for every service β€” rate, errors, duration β€” because that’s what the user experiences. USE for every resource behind it, because saturation is the leading indicator: queue depth and pool utilization rise before latency does, which is what makes them useful for autoscaling and for paging early.”

Metric types

Type Meaning Example Gotcha
Counter Monotonically increasing requests_total Always graph the rate, never the raw value; and handle resets on restart
Gauge Point-in-time value, up or down queue_depth, memory_bytes Sampled β€” you miss spikes between scrapes
Histogram Bucketed distribution request_duration_seconds Buckets are fixed at definition; a bad bucket layout means you can’t compute a useful p99
Summary Client-computed quantiles p99 per instance Not aggregatable across instances β€” this is why histograms win

⚠️ Prefer histograms over summaries. You cannot merge per-instance p99s into a fleet p99 (see Performance Metrics). Histograms merge; quantiles don’t.

Cardinality explosion β€” the #1 real-world observability incident

Every unique combination of label values is a separate time series.

http_requests_total{method, status, endpoint}
  5 methods Γ— 10 statuses Γ— 50 endpoints = 2,500 series          ← fine

...now add user_id:
  2,500 Γ— 1,000,000 users = 2,500,000,000 series                 ← your metrics
                                                                    system is dead

Never put these in a metric label: user ID, request ID, trace ID, email, session ID, full URL path with IDs in it (/orders/12345), raw error message, timestamp.

Where high-cardinality data belongs: logs and traces. They’re indexed for it; metrics aren’t.

❌ metric label: endpoint="/orders/12345"
βœ… metric label: endpoint="/orders/:id"     ← normalize the template
βœ… trace attribute / log field: order_id=12345

Say this: β€œI’d keep metric labels low-cardinality β€” normalized route templates, status class, region β€” and push per-request identifiers into traces and logs. Cardinality explosion from putting a user ID in a label is the most common way teams take down their own monitoring, right at the moment they need it.”


Logs

Structured, always

❌ log.info("User " + userId + " failed payment of " + amt + " reason " + r);
   β†’ unqueryable; you're writing regexes at 3am

βœ… log.info("payment_failed",
       Map.of("user_id", userId, "amount_cents", amt,
              "reason", r, "trace_id", traceId));
   β†’ queryable: reason:insufficient_funds AND amount_cents:>10000

Levels, used consistently

Level Meaning Alertable?
ERROR Something failed that needs a human eventually Sometimes (on rate, not per-event)
WARN Unexpected but handled β€” retry succeeded, fallback used No, but trend it
INFO Notable business events β€” order placed, user registered No
DEBUG Developer detail Off in prod, or sampled/dynamically enabled

⚠️ If everything is ERROR, nothing is. Log-level discipline is what makes β€œerror rate” a usable signal.

The costs people forget

Sampling strategy: log all errors, sample successes (1%), and support dynamic log levels so you can turn DEBUG on for one service or one user without a redeploy.


Distributed tracing

trace_id: abc123  (one user request, propagated through every hop)

[gateway ──────────────────────────────────────────── 450ms]
  [auth ── 20ms]
  [orders ─────────────────────────────────── 400ms]
     [db query ── 30ms]
     [inventory ────── 90ms]
     [payments ──────────────────── 260ms]     ← there it is
        [fraud-check ──────── 200ms]           ← actually, there
Term Meaning
Trace One end-to-end request
Span One unit of work within it (a service call, a DB query)
Parent span The span that caused this one β€” builds the tree
Trace context The headers propagated between services (W3C traceparent)
Baggage Key-value data carried along the whole trace (tenant ID, experiment group)

Propagation is the whole game. If any service drops the trace header, the trace breaks there and everything downstream looks like an unrelated trace. Instrument it once in a shared HTTP/gRPC client (or get it free from a service mesh) rather than per-endpoint β€” and remember queues: trace context must be carried in message headers too, or async work becomes invisible.

Sampling:

Strategy How Trade-off
Head-based Decide at the entry point (e.g. keep 1%) Cheap, simple β€” but you probably didn’t sample the one slow request you care about
Tail-based Buffer the full trace, decide after it finishes Keeps all errors and slow traces; needs a collector holding spans in memory

Say this: β€œTail-based sampling if the platform supports it β€” I want 100% of errors and slow traces and 1% of the boring ones. Head-based sampling at 1% is cheap but statistically guarantees you miss most of the rare failures you’re trying to debug.”


Alerting

Alert on symptoms, not causes

❌ "CPU > 80%"                 β†’ often fine, and doesn't mean users hurt
❌ "a pod restarted"           β†’ that's the system self-healing
❌ "disk 70% full"             β†’ not urgent unless it's growing fast

βœ… "checkout error rate > 1% for 5 min"      β†’ users are failing
βœ… "p99 latency > 1s for 10 min"             β†’ users are waiting
βœ… "queue depth growing for 15 min"          β†’ users will fail soon
βœ… "error budget burn rate 14Γ— normal"       β†’ we'll blow the SLO in 2 days

The test for a good alert: if it fires and nobody needs to do anything, it should not have fired. Every non-actionable page trains the on-call to ignore alerts β€” that’s alert fatigue, and it’s how real incidents get missed.

Symptom vs cause, in practice

Page on symptoms (user-visible). Route causes to dashboards and tickets. One symptom alert plus a good dashboard beats twenty cause alerts, because the twenty all fire together during an incident and bury the signal.

Burn-rate alerting (the SRE approach)

Instead of β€œerror rate > 1%,” alert on how fast you’re consuming the error budget:

SLO: 99.9% over 30 days β†’ error budget = 43 minutes

Burn rate 1Γ—   β†’ you'll exactly use the budget in 30 days   β†’ fine
Burn rate 14Γ—  β†’ budget gone in ~2 days                     β†’ page now
Burn rate 6Γ—   β†’ budget gone in ~5 days                     β†’ ticket, not a page

Two windows (a fast one to catch spikes, a slow one to catch slow bleeds) gives you high precision and recall β€” far better than a fixed threshold that either pages on every blip or misses a slow degradation.

Things worth alerting on that people forget


What to instrument in an HLD answer

When asked β€œhow would you monitor this?”, walk the request path:

Layer Watch
Edge / CDN Cache hit ratio, origin request rate, 4xx/5xx split, TLS errors
Load balancer Per-target health, surge queue length, rejected connections, target response time
Service RED per endpoint, saturation (thread pool, connection pool), GC pause time
Cache Hit ratio (the metric), eviction rate, memory used vs max, hot-key skew
Database p99 query time, slow query log, connection count vs max, replication lag, lock waits, transaction age
Queue Consumer lag / queue depth, oldest message age, DLQ depth, processing rate vs arrival rate
Business Orders/min, signups/min, payment success rate β€” the fastest detector of a subtle break

⚠️ Business metrics catch what technical metrics miss. A deploy that makes the checkout button invisible produces a perfect technical dashboard β€” 200s everywhere, low latency β€” while revenue goes to zero. β€œOrders per minute vs the same time last week” catches it in minutes. Mentioning this is a strong senior signal.

The two saturation metrics most worth alerting on: queue depth and connection pool utilization. Both rise before latency does, which makes them leading rather than lagging indicators.


Interview application

β€œHow would you know this system is healthy?” β€œRED metrics per service and USE per resource, with the SLO defined on the user-facing indicator β€” checkout success rate and p99 latency at the edge. I’d page on symptoms using multi-window burn-rate alerts against the error budget, not on CPU. For debugging, distributed tracing with W3C context propagated through HTTP and message headers, tail-sampled so I keep every error and slow trace, and structured logs carrying the trace ID so I can jump from a trace span straight to its logs.”

β€œA customer says the app is slow. Walk me through it.” β€œCheck whether it’s them or everyone: is the fleet p99 elevated, and does it slice to a region, client version, or tenant? If it’s global, find the slow hop in a trace β€” that gives me the service and often the specific call. Then the logs for that trace ID explain why. If the metrics look clean, I suspect something outside my measurement: DNS, mobile radio latency, or a client-side regression β€” which is why I want RUM at the client, not just server-side timing.”

β€œHow do you avoid alert fatigue?” β€œOnly page on symptoms that need immediate human action, use burn-rate windows so brief blips don’t page, route everything else to dashboards and tickets, and review every page in the postmortem asking β€˜was this actionable?’ β€” deleting alerts is real work that teams under-invest in.”


Common Interview Questions

Q: β€œMetrics, logs, or traces β€” where do you start?” A: β€œMetrics to detect and quantify, traces to localize, logs to explain. They’re a workflow, not alternatives. If I could only have one for a distributed system I’d take traces, because they contain enough structure to derive rough metrics, whereas metrics can never tell me where in a call graph the time went.”

Q: β€œWhat’s cardinality explosion?” A: β€œEvery unique label-value combination is a separate time series. Adding a user ID label to a metric multiplies your series count by your user count and kills the metrics backend β€” usually during an incident, when you need it most. High-cardinality identifiers belong in traces and logs; metric labels should be normalized route templates and low-cardinality dimensions.”

Q: β€œWhy not alert on CPU?” A: β€œHigh CPU often means good utilization, and low CPU can coexist with terrible latency from lock contention or a saturated connection pool. CPU is a cause, not a symptom. I’d alert on user-visible symptoms and keep CPU on the dashboard for diagnosis.”

Q: β€œHow do you trace a request through async processing?” A: β€œPropagate trace context in the message headers, not just HTTP headers, and link the consumer span to the producer span. Without that, everything after the queue looks like an unrelated trace and the async half of your system is invisible.”

Q: β€œWhat’s the most important cache metric?” A: β€œHit ratio β€” it directly tells me whether the cache is earning its cost. Paired with eviction rate: a falling hit ratio with a high eviction rate means the cache is too small, whereas a falling hit ratio with low evictions means the access pattern changed or TTLs are too short.”

Q: β€œHow much do you sample?” A: β€œTail-based: 100% of errors and slow traces, ~1% of successes. Head-based sampling at a flat 1% is cheaper but means you almost never captured the rare slow request you’re being asked about.”

Q: β€œWhat would you alert on for a Kafka consumer?” A: β€œConsumer lag and its trend β€” absolute lag spikes during a deploy and that’s fine, but lag growing steadily means arrival rate exceeds processing rate and you’ll never catch up. Also oldest-message age, DLQ depth above zero, and the absence of processing metrics entirely, since a dead consumer emits nothing and looks calm.”


Decision Framework

Question Use
Is the system broken right now? Metrics + symptom alerts
Which service is slow? Distributed trace
Why did this specific request fail? Logs, found via trace ID
Should this page someone? Only if a human must act now
Where does a user ID belong? Trace attribute or log field β€” never a metric label
How do I catch a silent functional break? Business metrics vs same-time-last-week
Am I about to be overloaded? Saturation: queue depth, pool utilization
Is async processing keeping up? Consumer lag trend + oldest message age
How do I set the alert threshold? Error-budget burn rate, multi-window

← Back to Fundamentals ← Unique ID Generation Terminology Reference β†’

Free system design + DSA prep. If it helped you crack an interview, consider supporting.