Fun with Learning Technology
LearnCoursesQuestionsTracksToolsNewsExplorePractice
Fun with Learning Technology

A new problem, explained clearly, every day.

Subscribe
Learn
  • Lessons
  • Topics
  • News
  • Tools
  • Courses
  • Career tracks
  • Everything
Site
  • About
  • Contact
  • Support
  • Privacy
  • Terms
Get the daily one

One email per new problem. No spam.

Request a tutorial

Requests shape what gets made next.

© 2026 Fun with Learning TechnologyRSS
Home›Courses›System Design›Monitoring, Alerting, and Observability

Reliability

Monitoring, Alerting, and Observability

Observability provides visibility into the internal state of a distributed system by analyzing telemetry data like logs, metrics, and traces. It is essential for minimizing mean time to recovery (MTTR) by allowing engineers to diagnose complex failures in production environments. You reach for these patterns whenever a system grows beyond a single process and individual inspection of logs becomes impossible.

The Foundation: Metrics and Time-Series Data

Metrics are numerical representations of system behavior over time, collected at regular intervals. They are the bedrock of observability because they allow for efficient aggregation, alerting, and trend visualization without the massive storage overhead of raw logs. When you track metrics, you are effectively reducing high-dimensional event streams into manageable time-series data points. This data enables engineers to reason about system health via quantitative signals like request rates, latency distributions, and saturation levels. By calculating rates of change or average values across clusters, you can distinguish between transient noise and genuine infrastructure degradation. The power of metrics lies in their ability to provide a birds-eye view of your distributed environment. If you do not instrument your code to track these fundamental signals, you are essentially flying blind when traffic patterns shift or hardware performance fluctuates, making it impossible to perform proactive capacity planning.

# Simple metric counter implementation using an in-memory approach
import time

class MetricsCollector:
    def __init__(self):
        self.counters = {}

    def increment(self, metric_name):
        # Tracking occurrences of events like 'requests_total'
        self.counters[metric_name] = self.counters.get(metric_name, 0) + 1

    def get_metrics(self):
        return self.counters

# Usage:
collector = MetricsCollector()
collector.increment("api_requests_total")
print(f"Current metrics state: {collector.get_metrics()}")

Structured Logging for Granular Context

While metrics tell you that something happened, logs tell you exactly why it happened. Structured logging elevates raw text by organizing data into machine-readable formats, typically JSON, which allows for complex filtering and aggregation. In a distributed system, a single user request might traverse multiple services, making it difficult to reconstruct the sequence of events without a shared identifier. By injecting a correlation ID into every log entry at the entry point, you can stitch together disparate events across microservices. This is crucial for debugging edge cases where only a specific sequence of operations triggers a failure. You should aim to include sufficient metadata—such as user IDs, request durations, and error codes—within the log structure. This enables you to query logs based on specific attributes, transforming them into a powerful diagnostic tool that bridges the gap between high-level metric anomalies and granular root cause analysis.

import json
import time

def log_event(level, message, **kwargs):
    # Structured logging provides machine-readable output for log aggregators
    entry = {
        "timestamp": time.time(),
        "level": level,
        "message": message,
        **kwargs
    }
    print(json.dumps(entry))

# Usage:
log_event("INFO", "User logged in", user_id="u123", session_id="s99")

Distributed Tracing: Mapping Request Flows

Distributed tracing is the most advanced pillar of observability, designed to map the causal path of a request as it travels through a complex architecture. Unlike logs, which are often siloed by service, traces track a single transaction through the entire call graph, capturing start times, end times, and parent-child relationships between operations. This is invaluable when reasoning about system latency in environments where a request may fan out to dozens of downstream dependencies. By visualizing these paths, engineers can identify 'bottleneck services' that contribute disproportionately to total response time. Tracing works by passing a 'trace context' header across network boundaries, allowing each participant in the request lifecycle to report its specific segment. Without this deep visibility, diagnosing 'long tail' latency issues in distributed environments becomes a game of guesswork, as the source of the delay is often buried deep within the orchestration layer of the system.

class Span:
    def __init__(self, name, trace_id):
        self.name = name
        self.trace_id = trace_id
        self.start_time = None

    def start(self):
        self.start_time = time.time()

    def end(self):
        duration = time.time() - self.start_time
        # In a real system, this span would be sent to a collector service
        print(f"Trace {self.trace_id}: {self.name} took {duration:.4f}s")

# Example usage showing a span within a trace context
span = Span("db_query", "trace_abc_123")
span.start()
time.sleep(0.05) # Simulated operation
span.end()

Alerting: Converting Signals into Action

Alerting is the automated mechanism that notifies humans when the system deviates from predefined performance boundaries. The core challenge in alerting is preventing 'alert fatigue,' which occurs when systems trigger notifications for non-actionable issues. Effective alerting relies on Service Level Objectives (SLOs) and Error Budgets. Instead of alerting on every single failed request, you should alert on the rate of failure or the impact on user experience over a rolling window. By focusing on symptoms rather than causes—alerting when users cannot complete a checkout rather than when a specific server process spikes in CPU—you ensure that human intervention is reserved for genuine incidents. A well-designed alerting pipeline filters noise, groups related events, and escalates based on the urgency of the issue. When designing these systems, always prioritize reliability; an alerting system that fails during an incident is worse than having no alerting at all.

def check_alert(error_rate, threshold=0.05):
    # Alert only if error rate exceeds the defined SLO threshold
    if error_rate > threshold:
        return "ALERT: Incident triggering page to on-call engineer"
    return "System healthy"

# Evaluate against a 5% error budget
print(check_alert(0.07))

The Feedback Loop: Observability Driven Development

Observability-Driven Development (ODD) is a philosophy where the ability to observe the system is integrated into the software lifecycle from the very first line of code. It changes how you write software because you stop viewing logging and metrics as an afterthought added during production preparation. Instead, you design your services to emit rich, high-cardinality telemetry data that explains what the service is thinking, what dependencies it is calling, and what errors it encountered. This mindset shift reduces the 'unknown unknowns' during incidents, as you have already instrumented the code to provide deep visibility into its operation. By creating a feedback loop where observability data drives future feature prioritization and architectural refactoring, you ensure the system remains resilient as it scales. Ultimately, observability is about empowering engineers to reason about complexity through data, allowing them to remain confident in their code even as the architecture inevitably becomes more intricate.

def process_transaction(amount):
    # The function includes its own instrumentation to ensure observability
    try:
        # Simulated business logic
        return True
    except Exception as e:
        # Rich context allows for better debugging during production incidents
        log_event("ERROR", "Transaction failed", amount=amount, error=str(e))
        return False

process_transaction(100.50)

Key points

  • Metrics provide aggregated time-series data for monitoring system health trends.
  • Structured logging enables machine-readable context during diagnostic investigation.
  • Distributed tracing maps the causal request path across multiple service boundaries.
  • Alerting should focus on user-impacting symptoms rather than individual component errors.
  • Correlation IDs are essential for stitching logs together in a distributed environment.
  • High-cardinality data allows for granular filtering when debugging complex production issues.
  • Service Level Objectives (SLOs) define the thresholds that trigger actionable alerts.
  • Observability-Driven Development integrates telemetry into the design phase of software engineering.

Common mistakes

  • Mistake: Alerting on every individual system metric. Why it's wrong: Leads to alert fatigue and misses the user impact. Fix: Focus on Symptoms-based alerting (latency, errors, saturation) rather than Causes (CPU usage).
  • Mistake: Confusing monitoring with observability. Why it's wrong: Monitoring tells you that a system is broken; observability lets you understand why. Fix: Implement structured logging and distributed tracing to provide internal state visibility.
  • Mistake: Setting static thresholds for dynamic workloads. Why it's wrong: Static limits break during traffic spikes or scheduled batch jobs. Fix: Use anomaly detection or percentiles (e.g., P99 latency) to account for variability.
  • Mistake: Storing too much high-cardinality data indefinitely. Why it's wrong: Causes massive storage costs and slow query performance. Fix: Implement data retention policies and use summary metrics for long-term trends.
  • Mistake: Neglecting the 'unknown unknowns'. Why it's wrong: You cannot write alerts for failures you haven't anticipated. Fix: Invest in exploration tools like distributed tracing and log aggregation to investigate emergent behaviors.

Interview questions

What is the fundamental difference between monitoring and observability in a distributed system?

Monitoring is essentially a dashboard-focused practice that tells you when something is wrong. It focuses on 'known unknowns'—predefined metrics like CPU usage or error rates that you expect to track. Observability, however, is a property of the system that allows you to understand 'unknown unknowns.' It uses telemetry data—specifically logs, metrics, and distributed traces—to ask arbitrary questions about internal states without needing to have predicted the failure mode in advance.

Why are distributed traces critical for debugging microservices architectures?

In a monolithic system, you can often trace a request through a single stack trace. In microservices, a single request might traverse dozens of services. Without distributed tracing, it is nearly impossible to identify which service caused a latency spike or a failure. By attaching a unique trace ID to a request header, you can visualize the entire call chain, effectively pinpointing whether the bottleneck exists in the database query, a third-party API call, or a specific service's logic.

Compare the push-based versus pull-based monitoring models. When would you choose one over the other?

The pull model, where a central server scrapes metrics from individual endpoints, is excellent for service discovery and configuration management; the server controls the scrape rate, preventing service overload. The push model, where services send metrics to a collector, is better for ephemeral or short-lived tasks like serverless functions or batch jobs that may vanish before a scraper can reach them. Choose pull for standard stable clusters and push for highly dynamic or serverless architectures.

How do you design an alerting system to minimize 'alert fatigue' while ensuring critical issues are never missed?

Alert fatigue happens when systems generate too much noise. To solve this, design alerts based on symptoms—such as 'user latency too high'—rather than causes, like 'CPU usage at 90%.' CPU spikes are often temporary and non-critical. Additionally, implement alert aggregation and silencing, ensuring that if a dependent service fails, you receive one incident report for the outage rather than five hundred individual alerts for every downstream service that timed out as a result.

How would you implement log aggregation for a high-traffic system that generates terabytes of data daily?

For high-volume logging, avoid writing directly to a database, as the write load will crash your monitoring infrastructure. Use a buffer like a distributed message queue (e.g., Kafka) to ingest logs from collectors (e.g., Fluentd) running on your nodes. Then, use a streaming processor to parse, filter, and aggregate logs before indexing them into a search engine. This design allows you to handle backpressure and provides a scalable buffer, preventing data loss during traffic spikes.

Describe a strategy for setting up Service Level Objectives (SLOs) and Error Budgets for a core payment service.

An SLO defines the target reliability for a service, usually expressed as a percentage of successful requests, like 99.9% uptime. The error budget is the remainder—in this case, 0.1% downtime—where you can afford to fail. If you burn through your error budget by shipping risky updates, you must immediately halt new feature releases and focus entirely on reliability improvements. This creates a data-driven agreement between engineering and product teams regarding how much risk the system can reasonably tolerate.

All System Design interview questions →

Check yourself

1. Which approach best describes the 'Golden Signals' methodology in observability?

  • A.Focusing strictly on infrastructure CPU, memory, and disk usage.
  • B.Prioritizing latency, traffic, errors, and saturation.
  • C.Alerting on every single service failure immediately.
  • D.Tracking developer commit frequency and build times.
Show answer

B. Prioritizing latency, traffic, errors, and saturation.
The Golden Signals focus on user-facing outcomes. Option 0 focuses on causes, not symptoms. Option 2 leads to noise and fatigue. Option 3 measures productivity, not system health. Option 1 is the standard for service-level observability.

2. When designing a distributed system, why is distributed tracing superior to simple log aggregation for troubleshooting?

  • A.It generates less data than logs.
  • B.It provides a complete visualization of a request's path across service boundaries.
  • C.It allows for direct modification of service configuration files.
  • D.It completely replaces the need for metric dashboards.
Show answer

B. It provides a complete visualization of a request's path across service boundaries.
Tracing correlates events across services using trace IDs, which is essential in microservices. Option 0 is false, tracing is often data-heavy. Option 2 is a security risk/out of scope. Option 3 is incorrect because metrics serve a different purpose (aggregation).

3. What is the primary danger of setting alerts based solely on average CPU utilization across a cluster?

  • A.It is too cheap to monitor.
  • B.It masks individual node failures or 'hot spots' through aggregation.
  • C.It generates too few alerts for engineers to react to.
  • D.It causes the database to perform slower.
Show answer

B. It masks individual node failures or 'hot spots' through aggregation.
Averages hide outliers; one stuck node could be failing while the average looks healthy. Option 0 is irrelevant. Option 2 is the opposite of the truth. Option 3 is unrelated to the alerting mechanism.

4. Why is 'High Cardinality' a critical factor when choosing a metrics database?

  • A.It allows for better compression of static configuration data.
  • B.It enables deep slicing and dicing of metrics by unique attributes like UserID or RequestID.
  • C.It guarantees that the system will never experience latency.
  • D.It increases the total number of physical servers needed.
Show answer

B. It enables deep slicing and dicing of metrics by unique attributes like UserID or RequestID.
High cardinality allows you to filter metrics by specific dimensions. Option 0 is false (cardinality increases storage complexity). Option 2 is unrelated. Option 3 is wrong as it is a database feature, not a hardware scaling strategy.

5. In the context of System Design, what is the 'Error Budget' used for?

  • A.To define the exact number of dollars saved by preventing downtime.
  • B.To balance the need for rapid feature deployment with system reliability.
  • C.To calculate the total cost of cloud infrastructure monitoring tools.
  • D.To determine how many engineers should be on call.
Show answer

B. To balance the need for rapid feature deployment with system reliability.
Error budgets allow teams to innovate up to a defined reliability threshold. Option 0 is a business metric, not a reliability one. Option 2 is a management decision. Option 3 is the core purpose of SRE (Site Reliability Engineering) as taught in System Design.

Take the full System Design quiz →

← PreviousFault Tolerance and Graceful DegradationNext →Disaster Recovery and Backup Strategies

System Design

31 lessons, free to read.

All lessons →

Track your progress

Sign in to mark lessons done, score quizzes and keep notes.

Open in the app