Skip to content
SparkTime, home
System Design From Zero to Interview-Ready
Lessons
1 · System Design Foundations
2 · APIs and Communication
3 · Capacity Estimation
4 · Core Building Blocks
5 · Databases
6 · Distributed Systems
7 · Reliability and Operations
8 · Architecture and Security
9 · Interview Method and Foundation Case Studies
10 · Real-World Case Studies
11 · Large-Scale Case Studies
12 · Interview Readiness
Lesson 25 of 45

Lesson 25 · Reliability and Operations

Observability: Logs, Metrics, Traces, SLOs and Alerts

If you cannot observe a system, you cannot operate it. Learn the three signals (structured logs, metrics and distributed traces), the golden signals and RED and USE methods, percentiles and histograms, trace context propagation and OpenTelemetry, SLIs, SLOs and error-budget alerts, health checks and synthetic monitoring, and how to observe asynchronous systems without drowning in data.

On this page

Key takeaways

  • Logs record individual events, metrics count and measure over time, and traces follow one request across services; you need all three, connected by shared ids.
  • Watch the golden signals for every service: latency (as percentiles), traffic, errors and saturation; aggregate latency with histograms, never by averaging percentiles.
  • Distributed tracing works by propagating a trace context through every call and message; OpenTelemetry provides vendor-neutral instrumentation for it.
  • Define SLIs and SLOs from what users experience, and alert on symptoms and error-budget burn, not on every internal cause.
  • Observability has a cost: control log volume, metric cardinality and trace sampling deliberately.

Users are complaining on social media that Pixly is slow. The team's dashboards are green: average CPU is 40%, average latency is 120 ms, error rate is 0.2%. An engineer spends two hours searching logs on individual servers before discovering that one of eighteen API servers has a misbehaving network card, and every request it handles takes two seconds. One in eighteen users is having a terrible time; the averages hid it completely.

A system you cannot see into is a system you cannot run. Observability is the ability to understand what is happening inside a system from what it emits, well enough to answer questions you did not anticipate when you built it. This article covers what to emit, how to connect it, what to alert on, and how to keep the cost under control.

What you will learn

#
  • Monitoring versus observability.
  • The three signals: logs, metrics and traces, and what each is good for.
  • What to measure: the golden signals, RED and USE.
  • Percentiles and histograms, and a common aggregation mistake.
  • Distributed tracing and context propagation, and what OpenTelemetry is.
  • SLIs, SLOs, error budgets and burn-rate alerting.
  • Health checks and synthetic monitoring.
  • Observing queues and asynchronous work, and controlling observability cost.

Monitoring and observability

#

Monitoring answers questions you decided in advance: is CPU above 80%? Is the error rate above 1%? Observability is the broader property of being able to ask new questions when something unexpected happens: why are requests from Android version 7.2 in Mumbai slow since 14:00? OpenTelemetry's observability primer describes it as understanding a system from the outside by examining its outputs, which requires the system to be properly instrumented (Observability primer).

In practice, you get observability by emitting rich, connected signals and having tools to slice them.

The three signals

#

OpenTelemetry lists traces, metrics and logs (and baggage) among its stable signals (Signals).

Signal

What it is

Best for

Weakness

Logs

Timestamped records of individual events

Details of what happened in one place: errors, decisions, inputs

Huge volume; hard to see patterns across many events

Metrics

Numbers aggregated over time: counts, rates, distributions

Trends, dashboards, alerts; cheap to store

Lose individual detail; limited by label cardinality

Traces

The path of one request through every service, as timed spans

Finding where time goes and where errors start in a distributed call

Usually sampled; needs instrumentation everywhere

They are most powerful connected: an alert on a metric leads to example traces of slow requests, and each trace links to the logs written while handling it. The glue is shared identifiers, especially the trace id.

Logs

#

Structured, not free text

#
JSON
{"ts": "2026-10-04T21:02:11.482Z", "level": "ERROR", "service": "feed-api", "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736", "user_id_hash": "a91f...", "route": "GET /v1/feed", "status": 504, "duration_ms": 2003, "dependency": "recommendations", "message": "dependency timeout"}

Structured logs (key-value, usually JSON) can be searched, filtered and aggregated by field. Free-text lines like ERROR something went wrong for user 42 cannot, at scale.

What to log and what not to

#
  • Do log: errors with context, important state transitions (order paid, payment failed), security-relevant events (login failures, permission changes), and the identifiers needed to connect events (request id, trace id).
  • Do not log: passwords, tokens, full card numbers, secrets, or unnecessary personal data. OWASP's logging guidance lists such data among things to exclude, mask or protect (Logging Cheat Sheet).
  • Levels (ERROR, WARN, INFO, DEBUG) let you control volume; keep DEBUG off in production by default.

Ship logs to a central system so you can search across all servers. Keep audit logs (who did what, for compliance) separate, with stricter retention and integrity controls (article 28).

Metrics

#

Kinds of metric

#
  • Counter: only goes up (requests served, errors). You look at its rate.
  • Gauge: goes up and down (queue length, memory in use, connections open).
  • Histogram: counts observations in buckets (how many requests took 0 to 10 ms, 10 to 25 ms, and so on), from which percentiles can be computed and aggregated correctly across servers.

What to measure

#

Google's SRE book proposes four golden signals for every user-facing system (Monitoring Distributed Systems):

  1. Latency: time to serve requests, with successful and failed requests measured separately (a fast error is not a fast success).
  2. Traffic: demand, such as requests per second.
  3. Errors: the rate of failed requests, including explicit errors, wrong content, and requests that violate policy (for example, slower than the SLO).
  4. Saturation: how full the system is, with emphasis on the most constrained resource.

Two related checklists are widely used:

  • RED for services: Rate, Errors, Duration.
  • USE for resources (CPU, disks, connection pools, queues): Utilisation, Saturation, Errors.

Add business metrics: orders per minute, uploads per minute, sign-ups. They often reveal problems that technical metrics miss ("errors are flat, but orders have dropped 40%").

Labels and cardinality

#

Metrics carry labels (route, status, region). Every distinct combination of label values is a separate time series that must be stored. A label with a few values is fine; a label like user_id with millions of values creates millions of series and can overwhelm a metrics system. Put high-cardinality detail in logs and traces, not metric labels.

Percentiles, and how not to aggregate them

#

From article 02: report latency as percentiles. But be careful how you combine them. Suppose one busy, healthy server and one quieter server with a problem each compute their own p99. This Java program shows what happens if you average those per-server percentiles instead of computing the percentile over all requests (which is what histograms let you do).

Java
// File: PercentileDemo.javaimport java.util.ArrayList;import java.util.Collections;import java.util.List;import java.util.Locale;public class PercentileDemo {    static long percentile(List<Long> values, double p) {        List<Long> sorted = new ArrayList<>(values);        Collections.sort(sorted);        int rank = (int) Math.ceil(p / 100.0 * sorted.size());   // nearest-rank method        return sorted.get(Math.max(rank, 1) - 1);    }    public static void main(String[] args) {        List<Long> serverA = new ArrayList<>();   // busy, healthy server: 900 requests        for (int i = 0; i < 900; i++) {            serverA.add(20L + (i % 10));          // 20 to 29 ms        }        List<Long> serverB = new ArrayList<>();   // quieter server with a problem: 100 requests        for (int i = 0; i < 100; i++) {            serverB.add(i < 50 ? 30L : 2_000L);   // half of them take 2 seconds        }        List<Long> all = new ArrayList<>(serverA);        all.addAll(serverB);        long p99A = percentile(serverA, 99);        long p99B = percentile(serverB, 99);        System.out.println("p99, server A:            " + p99A + " ms");        System.out.println("p99, server B:            " + p99B + " ms");        System.out.println("average of the two p99s:  " + (p99A + p99B) / 2 + " ms (meaningless)");        System.out.println("true p99, all requests:   " + percentile(all, 99) + " ms");        System.out.println("true p95, all requests:   " + percentile(all, 95) + " ms");        double mean = all.stream().mapToLong(Long::longValue).average().orElse(0);        System.out.printf(Locale.US, "mean, all requests:       %.1f ms%n", mean);    }}
Code
p99, server A:            29 msp99, server B:            2000 msaverage of the two p99s:  1014 ms (meaningless)true p99, all requests:   2000 mstrue p95, all requests:   30 msmean, all requests:       123.6 ms

Three lessons in six lines. The average of percentiles (1,014 ms) corresponds to no real request. The true p99 shows that 1 in 100 users waits two seconds, while the p95 looks perfectly healthy, so the tail is concentrated. And the mean (124 ms) looks acceptable while 5% of all requests are terrible. This is the opening story: averages hid a sick server. Per-server percentiles on a dashboard, plus correctly aggregated histograms, would have revealed it in minutes.

Distributed tracing

#

In a system with many services, a slow request might be slow because of any of them. A trace records one request's journey as a tree of spans: each span is one operation (an HTTP call, a database query, a cache lookup) with a start time, duration, status and attributes, and a parent span.

Code
Trace 4bf92f35...  GET /v1/feed                          total 2,040 ms|-- auth.validate_token                          4 ms|-- feed.read_precomputed (Redis)                6 ms|-- photos.batch_get (PostgreSQL replica)       18 ms|-- recommendations.get                       2,003 ms   <- timeout`-- likes.batch_counts (Redis)                   5 ms

One glance shows where the time went.

Context propagation

#

For spans from different services to join one trace, each call must carry the trace context: the trace id and the id of the calling span. The W3C Trace Context standard defines a traceparent HTTP header with four fields: a version, a 32-hex-character trace id, a 16-hex-character parent span id, and trace flags, for example 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01 (W3C Trace Context). Every service reads the incoming header, creates child spans, and sends the header on its own outgoing calls. OpenTelemetry's documentation explains this as context propagation (Context propagation).

For asynchronous work, put the trace context into message metadata when publishing to a queue, so the consumer's spans connect to the request that caused them.

Sampling

#

Tracing every request at high volume is expensive. Systems sample:

  • Head sampling: decide at the start of a request (for example, keep 1%). Cheap, but you may miss the rare slow or failing requests you care about.
  • Tail sampling: buffer spans and decide after the request finishes, keeping all errors and slow requests plus a small share of normal ones. More useful, more infrastructure.

OpenTelemetry

#

OpenTelemetry is a vendor-neutral, open-source set of APIs, SDKs and a collector for producing and exporting traces, metrics and logs. You instrument code once and send the data to whichever backend you use: open-source tools (for example Prometheus for metrics, Jaeger for traces, Grafana for dashboards) or a cloud provider's or vendor's observability service. Many frameworks and libraries can be instrumented automatically.

A short Java illustration of manual instrumentation with the OpenTelemetry API (a fragment, not a complete program):

Java
Span span = tracer.spanBuilder("recommendations.get").startSpan();try (Scope scope = span.makeCurrent()) {    span.setAttribute("user.segment", segment);    return recommendationClient.fetch(userId, deadline);   // the client propagates traceparent} catch (TimeoutException e) {    span.setStatus(StatusCode.ERROR, "timeout");    return Recommendations.empty();                          // fallback (article 23)} finally {    span.end();}

SLIs, SLOs and alerting

#

From signals to objectives

#

Article 02 introduced SLIs (measured indicators), SLOs (targets) and SLAs (contracts), following Google's SRE book (Service Level Objectives). Observability makes them concrete:

SLI (how measured)

SLO (target)

Share of GET /v1/feed requests answered successfully, measured at the load balancer

99.9% over 30 days

Share of GET /v1/feed requests faster than 300 ms

99% over 30 days

Share of uploads whose thumbnails are ready within 30 seconds

99% over 30 days

Measure SLIs as close to the user as practical (load balancer logs, client telemetry, synthetic checks), because a server cannot report a request that never reached it.

Alert on symptoms

#

The SRE book's guidance is to alert on symptoms (users are affected: errors are high, latency is high) rather than every possible cause (CPU is high, one replica is lagging), because symptom alerts reflect real impact and cause alerts tend to be noisy (Monitoring Distributed Systems). Causes belong on dashboards for diagnosis.

Good alerts are:

  • Actionable: someone can do something now.
  • Urgent when they page: non-urgent issues become tickets, not 3 a.m. pages.
  • Linked to a runbook with first steps.
  • Rare enough to be trusted. Alert fatigue makes people ignore pages.

Burn-rate alerts

#

An error budget (article 24) can be burned fast or slowly. The SRE workbook recommends alerting on how quickly the error budget is being consumed, using combinations of a short and a long window so that a severe problem pages quickly and a slow leak is still caught without flapping (Alerting on SLOs). The intuition: if, at the current error rate, you would use the whole month's budget in two days, page someone now; if you would use it in three weeks, open a ticket.

Health checks and synthetic monitoring

#
  • Liveness and readiness checks tell orchestrators and load balancers whether to restart a process or send it traffic (article 08). Keep them shallow.
  • Synthetic monitoring runs scripted user journeys from outside the system every minute: sign in, load the feed, upload a test photo. It catches problems that internal metrics miss (DNS, certificates, CDN, a broken front end) and measures availability as users experience it.

Observing asynchronous systems

#

Queues hide problems because nothing fails visibly; work just waits. For every queue and stream, track:

  • depth or consumer lag (messages waiting);
  • age of the oldest message (the user-facing delay);
  • processing rate and error rate per consumer;
  • dead-letter queue size (article 11);
  • end-to-end latency from event creation to effect (for example "upload to thumbnail ready"), which is the real SLI for the asynchronous path.

The cost of observability

#

Observability data is not free. A quick estimate for Pixly's API:

Code
3,500 requests/s at peak x 5 log lines per request x 500 bytes per line= 8.75 MB/s, about 750 GB per day at that rate (less on average)

Keeping that for 30 days in a searchable log system is expensive. Control it:

  • Log levels and sampling: log every error, sample routine success logs.
  • Short retention for verbose logs; longer for metrics (which are compact) and audit logs (which are required).
  • Bounded metric cardinality.
  • Tail-based trace sampling that keeps the interesting requests.

Worked example: Pixly's observability plan

#

Area

What

Alert (page)

Dashboard only

Feed API

RED metrics per route, latency histograms, per-instance p99

Feed availability SLO burn rate; p99 above 300 ms for 10 minutes

Per-dependency latency, cache hit ratio

Uploads

Upload success rate; time to thumbnails ready

Upload success SLO burn rate; thumbnail lag over 5 minutes

Queue depth, worker errors

Database

Connections, replication lag, slow queries

Primary unavailable; lag over 30 seconds on replicas in rotation

CPU, IOPS, cache hit

Queues

Depth, oldest message age, DLQ size

Oldest message over 10 minutes; DLQ growing fast

Throughput per consumer

Edge

Synthetic journeys from three cities

Two consecutive synthetic failures

CDN hit ratio, TLS expiry countdown

Business

Uploads per minute, sign-ups per minute

Sharp drop compared with the same time last week

Trends

Tracing

OpenTelemetry in every service, context in queue messages, tail sampling keeping errors and slow requests

none

Service dependency map

Replaying the opening story: the per-instance p99 dashboard shows one server at 2 seconds; its traces show slow network spans; it is drained and replaced within minutes, and the feed latency SLO alert, not social media, is what told the team.

Failure modes

#

Failure

Effect

Mitigation

Only averages on dashboards

Tail problems invisible

Percentiles, histograms, per-instance views

Alerting on causes

Noisy pages, ignored alerts

Symptom and burn-rate alerts; causes on dashboards

No trace propagation through queues

Async work disconnected from requests

Context in message metadata

High-cardinality labels

Metrics system overloaded

Move detail to logs and traces

Secrets or personal data in logs

Security and privacy incidents

Redaction, structured fields, reviews

Monitoring in the same failure domain as the system

Blind during outages

External synthetic checks; monitoring outside the main region

Trade-offs

#

Choice

You gain

You pay

More logging

Detail for debugging

Storage, cost, noise, privacy risk

More metric labels

Finer slicing

Cardinality and cost

Higher trace sampling

More complete picture

Overhead and storage

Tighter alert thresholds

Earlier detection

More false alarms

Vendor-neutral instrumentation (OpenTelemetry)

Freedom to change backends

Some setup effort

Common mistakes

#
  • Dashboards of averages.
  • Paging on CPU, disk and every internal metric.
  • No request or trace ids, so logs cannot be correlated.
  • Not measuring the asynchronous path's end-to-end delay.
  • Logging tokens, passwords or full personal details.
  • Treating observability as an afterthought added after launch.

In an interview

#
  • In the Harden phase, give each main flow an SLI and an SLO.
  • Say "golden signals per service, latency as percentiles from histograms".
  • Mention distributed tracing with context propagated through calls and queues.
  • Name two or three alerts and why they are symptoms.
  • For async pipelines, name lag or oldest-message age as the key metric.

Exercises

#

Exercise 1

Choose the signal

#

Which signal (log, metric or trace) is best for each question, and why?

  1. How many checkout requests failed in the last hour, by error code?
  2. Why did order 77's checkout take 9 seconds?
  3. What exact validation error did the payment provider return for order 77?
  4. Is the p99 latency of the search API rising this week?

Exercise 2: Fix the alerts

#

A team pages on: CPU above 70% on any server, any ERROR log line, replication lag above 1 second, and disk above 80%. They are paged 30 times a week and have started ignoring pages. Redesign their alerting.

Exercise 3: Trace the queue

#

A user uploads a photo; the thumbnail appears 4 minutes later. Your traces show the upload request finishing in 300 ms, and nothing else. What is missing, and what would you instrument?

Exercise 4: Define SLOs

#

Define two SLIs and SLOs for a ride-hailing app's "request a ride" flow, say where you would measure them, and write one burn-rate style alert in words.

Show solutionHide solution
  1. Metric: a counter of checkout requests by status code, rated over time. Cheap, aggregatable, perfect for counts and trends.
  2. Trace: follow the request across services and see which span took the time.
  3. Log: the specific error detail recorded by the payment service when the provider responded, found via the trace id or order id.
  4. Metric: a latency histogram for the search API, with p99 computed over time.

Exercise 2

Show solutionHide solution

Turn causes into dashboards and keep only symptom pages:

  • Page on: SLO burn rate for availability and latency of key user flows (fast burn); synthetic journey failures; queue oldest-message age above the user-facing threshold; primary database unavailable.
  • Ticket (not page) on: slow SLO burn; disk forecast to fill within 7 days (a trend-based alert instead of a fixed 80%); sustained replication lag above a threshold that matters for read routing (and automatically remove lagging replicas from rotation).
  • Dashboard only: CPU per server, individual ERROR logs (aggregate them into an error-rate metric instead), lag spikes of a few seconds.
  • Add a runbook link to every page, review every page in a weekly meeting, and delete or tune alerts that were not actionable.

Exercise 3

Show solutionHide solution

The trace stops at the API because the trace context was not propagated into the photo.uploaded message, and the workers are not instrumented (or not linked). Instrument:

  • inject traceparent into message metadata when publishing; extract it in the consumer and start a span linked to the original trace;
  • spans for queue wait time (time between publish and consume), thumbnail generation and the storage write;
  • metrics: queue depth, oldest message age, worker throughput and errors, and an end-to-end histogram of "upload to thumbnail ready".

The likely findings are a queue backlog (oldest message age near 4 minutes) or slow workers, which the new spans and metrics will distinguish.

Exercise 4

Show solutionHide solution
  • Availability SLI: share of ride requests that receive a valid response (a matched driver, or a clear "no drivers available") within 15 seconds, measured from client telemetry plus the API gateway. SLO: 99.5% over 28 days.
  • Latency SLI: share of ride requests that get a match decision within 10 seconds, measured at the matching service's request handling (and cross-checked by synthetic requests in test areas). SLO: 95% over 28 days.

Burn-rate alert (in words): page the on-call engineer if, over both the last 5 minutes and the last hour, the failure rate of ride requests is high enough that the 28-day error budget would be exhausted in under two days at that rate; open a ticket if, over the last 6 hours and the last 3 days, it would be exhausted before the end of the period.

Where to go next

#

Sources & further reading

  1. opentelemetry.io
  2. opentelemetry.io
  3. opentelemetry.io
  4. w3.org
  5. sre.google
  6. sre.google
  7. sre.google
  8. cheatsheetseries.owasp.org
  • Guide · Technology

    Start Here: Your Roadmap from Zero to Job-Ready DSA

  • Guide · Technology

    Heaps and Priority Queues

  • Guide · Career

    A LinkedIn Profile That Gets Recruiter Messages