← Back to all questions
StaffObservabilityDistributed Systems

Design a Logging and Monitoring System

A Staff-level walkthrough of a centralized logging, metrics, and tracing platform that ingests 10M+ events/sec from hundreds of microservices — trading search freshness against storage cost with hot/warm/cold tiers, severity-based sampling, and a durable buffer that absorbs indexing slowdowns. The hard part is keeping the monitoring system observable itself: a watchdog alert and an out-of-band fallback that survive random instance termination. Follow it top to bottom, or jump to any step.

Level
Staff
Category
Observability · Infrastructure
Interview time
45 min
100% free · No login required
WHAT THIS QUESTION TESTS
·Tiered storage: search index for hot logs, object store for cold
·Severity-based sampling with dynamic upsampling on error spikes
·Distributed tracing correlated with logs by trace_id
·Alert dedup, escalation, and auto-resolve across channels
★ STAFF-LEVEL SIGNALS
Buffers ingestion through a durable log so indexer slowdowns never drop events
Quantifies cost: 90-day retention in the search cluster vs. tiering to object storage
Picks a write-optimized store for traces (lookup by trace_id) over a search engine
Makes the monitoring system self-monitoring: watchdog alert plus fallback in a separate account
1

Requirements

Functional Requirements

  • Log ingestion: Collect logs from 700+ microservices running across AWS (EC2, containers)
  • Structured logging: All logs emitted as structured JSON with standard fields (timestamp, service, severity, trace_id, message, metadata)
  • Distributed tracing: Trace a single user request across 20+ microservices (e.g., Zuul gateway -> API -> Recommendation -> Model Serving -> Feature Store)
  • Search: Full-text search across logs with filters (service, severity, time range, trace_id)
  • Alerting: Trigger alerts based on log patterns, metric thresholds, and anomaly detection
  • Dashboards: Real-time visualization of metrics, log aggregates, and trace waterfalls
  • Retention: Configurable retention per log type and severity (7 days hot, 30 days warm, 90 days cold)

Non-Functional Requirements

  • Throughput: Ingest 10M+ log events/sec across all microservices
  • Latency: Logs searchable within 30 seconds of emission
  • Scale: Petabytes of log data; hundreds of billions of time-series data points in Atlas
  • Durability: No loss of ERROR/FATAL logs; best-effort for DEBUG
  • Availability: 99.9% for search/dashboards; 99.99% for ingestion and alerting (must monitor even during outages)

Constraints

  • Netflix runs entirely on AWS (no GCP/Azure) -- all storage and compute choices must be AWS-native or self-managed on EC2
  • 700+ microservices with heterogeneous tech stacks (Java, Node.js, Python)
  • Chaos engineering culture (Chaos Monkey) means monitoring must be resilient to random instance termination
  • Read-heavy for dashboards (thousands of engineers viewing dashboards simultaneously) but write-heavy for ingestion
2

Back-of-envelope estimation

Metric
Estimate
Microservices
700+
Log events/sec
10M
Average log size
500 bytes
Ingestion bandwidth
10M x 500B = 5 GB/s
Daily storage (raw)
5 GB/s x 86,400 = ~430 TB/day
With compression (5x zstd)
~86 TB/day
7-day hot tier (Elasticsearch)
~600 TB
30-day warm tier
~2.6 PB
90-day cold tier (S3)
~7.7 PB
Index size (~20% of raw)
~500 TB
Atlas metrics data points/sec
~2B (counters, gauges, distributions across all services)
Trace spans/sec
~5M (avg 5 spans per traced request, 10% sampling)
3

API design

# Ingest logs (batch)
POST /api/v1/logs
Body: { logs: [{ timestamp, service, severity, message, trace_id, span_id, host, metadata: {} }] }
Response: { accepted: 1000, failed: 0 }
 
# Search logs
GET /api/v1/logs/search?q={query}&service={name}&severity=ERROR&trace_id={id}&from={ts}&to={ts}&limit=100
Response: { logs: [...], total_count, next_cursor }
 
# Query traces
GET /api/v1/traces/{trace_id}
Response: { trace_id, spans: [{ span_id, parent_span_id, service, operation, duration_ms, status, tags }], total_duration_ms }
 
# Create alert rule
POST /api/v1/alerts
Body: { name, type: "threshold|rate_change|anomaly|missing_data", query, threshold, window_minutes, channels: ["pagerduty", "slack"] }
Response: { rule_id, status: "active" }
 
# Get metrics (Atlas-compatible)
GET /api/v1/metrics?name=server.requests&service={name}&tags=status:500&from={ts}&to={ts}&resolution=1m&aggregation=sum
Response: { metric, datapoints: [{ timestamp, value }] }
 
# Dashboard queries
POST /api/v1/dashboards/query
Body: { panels: [{ metric, filters, aggregation, group_by }] }
Response: { panels: [{ series: [...] }] }
4

Data model

Log Storage: Elasticsearch (hot) + S3 (cold)

Hot tier (0-7 days): Elasticsearch on i3 instances (NVMe SSDs)
Index pattern: logs-{service}-{YYYY.MM.dd}
Fields: @timestamp, service, severity, message, host, trace_id, span_id,
az, instance_id, container_id, metadata.*
Shards: 5 primary + 1 replica per daily index
 
Warm tier (7-30 days): Elasticsearch on d2 instances (HDD, force-merged)
Read-only indices, reduced replicas
 
Cold tier (30-90 days): S3 in Parquet format
Partitioned by: date/service/severity/
s3://netflix-logs-cold/{date}/{service}/{severity}/part-{n}.parquet
Compressed with zstd
Queryable via Presto/Athena for ad-hoc analysis

Why Elasticsearch for hot tier? Full-text search on log messages is the primary query pattern. ES provides near-real-time indexing (~1s refresh) and powerful query DSL. Netflix engineers need to search by arbitrary text patterns during incidents.

Metrics: Atlas (Netflix Custom TSDB)

Atlas metric format:
name: server.requestCount
tags: { nf.app: "api-gateway", nf.cluster: "api-gateway-v042",
nf.asg: "api-gateway-v042-us-east-1a", status: "200", method: "GET" }
type: counter | gauge | distribution_summary | timer
 
Storage: In-memory for last 6 hours (sub-second queries)
LevelDB on local SSD for 24 hours
S3 for long-term (downsampled to 1-min resolution)

Why Atlas (custom) over Prometheus? Netflix’s scale (2B+ data points/sec) exceeds single-node Prometheus capacity. Atlas is designed for dimensional time-series at Netflix’s scale, with a custom query language (Atlas Stack Language) that supports real-time streaming evaluation.

Trace Storage: Cassandra

Keyspace: distributed_tracing
 
CREATE TABLE traces (
trace_id UUID,
span_id UUID,
parent_span_id UUID,
service TEXT,
operation TEXT,
start_time TIMESTAMP,
duration_ms INT,
status TEXT,
tags MAP<TEXT, TEXT>,
PRIMARY KEY (trace_id, start_time, span_id)
) WITH CLUSTERING ORDER BY (start_time ASC)
AND default_time_to_live = 604800; -- 7-day TTL

Why Cassandra? Write-optimized for high-throughput span ingestion. Trace queries are always by trace_id (partition key), making Cassandra’s key-value lookup pattern ideal. Netflix already operates Cassandra at massive scale across AWS.

Alert Rules: PostgreSQL (RDS)

CREATE TABLE alert_rules (
rule_id SERIAL PRIMARY KEY,
name VARCHAR(200),
type VARCHAR(30), -- threshold, rate_change, anomaly, missing_data
query TEXT, -- Atlas query or log query
threshold FLOAT,
window_minutes INT,
severity VARCHAR(20), -- critical, warning, info
channels TEXT[], -- pagerduty, slack, email
owner_team VARCHAR(100),
runbook_url TEXT,
cooldown_minutes INT DEFAULT 15,
enabled BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP,
updated_at TIMESTAMP
);
 
CREATE TABLE alert_history (
alert_id BIGSERIAL PRIMARY KEY,
rule_id INT REFERENCES alert_rules(rule_id),
fired_at TIMESTAMP,
resolved_at TIMESTAMP,
severity VARCHAR(20),
message TEXT,
services TEXT[],
acknowledged_by VARCHAR(100)
);
5

High-level architecture

┌───────────────┐ ┌────────────┐ ┌──────────────────────┐
│ 700+ Netflix │────▶│ Sidecar │────▶│ Kafka │
│ Microservices│ │ Agent │ │ (log + span topics)│
│ (Java/Node/ │ │ (Fluentd) │ └──────────┬───────────┘
│ Python) │ └────────────┘ │
└───────┬───────┘ ┌─────────────┼──────────────────┐
│ │ │ │
│ ┌─────┴──────┐ ┌────┴──────┐ ┌──────┴───────┐
│ │ Indexer │ │ Alert │ │ Archiver │
│ │ Workers │ │ Evaluator│ │ (→ S3) │
│ └─────┬──────┘ └────┬──────┘ └──────┬───────┘
│ │ │ │
│ ┌─────┴──────┐ ┌────┴──────┐ ┌──────┴───────┐
│ │Elasticsearch│ │ PagerDuty │ │ S3 │
│ │ (hot logs) │ │ Slack │ │ (cold logs) │
│ └────────────┘ └───────────┘ └──────────────┘
│ ┌──────────────────────────────────────────────────────┐
├──▶ Atlas Pipeline (Metrics) │
│ │ Services → Atlas Agent → Atlas Backend (in-memory) │
│ │ └──▶ Atlas Dashboards (Grafana-like UI) │
│ │ └──▶ Alert Evaluator (streaming) │
│ └──────────────────────────────────────────────────────┘
│ ┌──────────────────────────────────────────────────────┐
└──▶ Tracing Pipeline (Edgar) │
│ Services → Mantis (stream processing) → Cassandra │
│ └──▶ Edgar UI (trace viewer + waterfall) │
└──────────────────────────────────────────────────────┘

Data Flow: Log Ingestion

1. Each microservice emits structured JSON logs to stdout/stderr

2. Sidecar agent (Fluentd) on each host reads logs, buffers locally (disk-backed)

3. Agent batches logs (every 5 seconds or 1000 events) and forwards to Kafka log topics

4. Kafka partitions logs by service name for processing locality

5. Indexer workers (Kafka consumers) bulk-index logs into Elasticsearch (batch of 5000 docs)

6. Archiver (separate consumer group) writes logs in Parquet format to S3 for cold storage

7. Alert Evaluator (streaming consumer) evaluates log-based alert rules in real-time

Data Flow: Distributed Trace

1. User request hits Zuul API gateway, which generates a trace_id (UUID) and first span

2. trace_id propagated via HTTP headers (X-Netflix-TraceId) through all downstream service calls

3. Each service emits span data: { trace_id, span_id, parent_span_id, service, operation, duration_ms }

4. Spans published to Kafka trace topic

5. Mantis stream processor aggregates spans by trace_id

6. Completed traces written to Cassandra (partitioned by trace_id)

7. Edgar UI queries Cassandra to render waterfall visualization

Data Flow: Metrics

1. Application code instruments metrics using Atlas client library: registry.counter(“server.requests”).increment()

2. Atlas agent on each host aggregates metrics locally (1-minute windows)

3. Aggregated data pushed to Atlas backend cluster (in-memory time-series store)

4. Atlas backend serves real-time dashboard queries with sub-second latency

5. Older data (6h+) flushed to SSD; data older than 24h archived to S3 with downsampling

6

Deep dives

WHERE STAFF IS WON

Deep Dive 1: Log Ingestion Pipeline

Sidecar Agent (per host):

  • Fluentd with Netflix-specific plugins runs as a sidecar on every EC2 instance / container
  • Local disk buffer (2 GB) prevents data loss if Kafka is temporarily unavailable
  • Agent enriches logs with instance metadata: instance_id, availability_zone, asg_name, ami_id (from EC2 instance metadata service)
  • Batching: send every 5 seconds or 1000 events, whichever comes first

Kafka cluster design:

  • Dedicated Kafka cluster for logging (separate from application Kafka clusters)
  • Topics: logs.{service} (one topic per service for isolation)
  • Partitions: 100+ per topic for high-throughput services, 10 for lower-throughput services
  • Retention: 72 hours (allows replay if indexer falls behind or needs reprocessing)
  • Replication factor: 3 across AZs for durability

Indexer workers:

  • Stateless Kafka consumers running on EC2 Auto Scaling Groups
  • Bulk index to Elasticsearch: 5000 documents per request
  • Back-pressure: if ES bulk request latency > 5s, consumer pauses (Kafka handles buffering)
  • Idempotent writes using Kafka offset as ES document version

Sampling strategy (critical for managing 10M events/sec):

Severity
Sample Rate
Rationale
FATAL
100%
Always retain — indicates service death
ERROR
100%
Always retain — every error matters
WARN
100%
Retain — early indicators of problems
INFO
10%
Reduce volume 10x, keep representative sample
DEBUG
1%
Massive volume, only useful during investigations

Dynamic sampling: If error rate for a service exceeds 2x its baseline, automatically increase INFO/DEBUG sampling to 100% for that service for 15 minutes. Implemented via Mantis stream processing rule that pushes sampling config updates to sidecar agents.

Deep Dive 2: Search Architecture + Elasticsearch Cluster Design

Elasticsearch cluster topology (across 3 AWS AZs):

Tier
Node Type
Instance
Storage
Data Age
Purpose
Hot
Data nodes (60)
i3.2xlarge
NVMe SSD
0-24 hours
Active indexing + search
Warm
Data nodes (40)
d2.2xlarge
HDD
1-7 days
Search, no indexing
Frozen
S3-backed
n/a
S3
7-90 days
Occasional ad-hoc queries
Master
Dedicated (3)
m5.xlarge
EBS
n/a
Cluster management

Index Lifecycle Management (ILM):

Hour 0: Create index logs-{service}-{date} (hot tier, 5 primary + 1 replica)
Hour 24: Force merge to 1 segment, move to warm tier (reduce to 1 replica)
Day 7: Snapshot to S3, convert to frozen/searchable snapshot
Day 30: Delete from ES cluster entirely
Day 90: Delete Parquet files from S3 (unless compliance requires longer)

Search optimization:

  • Time-range filtering applied first (most queries during incidents include time range)
  • Index-per-day-per-service pattern means searches only hit relevant indices
  • Keyword fields for exact match (service, severity, host) vs. text fields for full-text (message)
  • Use routing by trace_id so all logs for a trace land on the same shard (faster trace-based queries)
  • ES query circuit breaker: reject queries that would scan > 500 shards (force users to add time/service filters)

Edgar integration (Netflix’s distributed tracing UI):

  • Edgar queries both Cassandra (trace spans) and Elasticsearch (logs) by trace_id
  • Unified view: trace waterfall + correlated log lines for each span
  • Critical for incident debugging: "Why is this request slow?" -> trace shows which service, logs show why

Deep Dive 3: Alerting Engine + Chaos-Resilient Monitoring

Architecture: Dual evaluation pipeline for resilience

Stream path (real-time, seconds):
Kafka log topic ──▶ Mantis Stream Processor ──▶ Alert matches ──▶ Notification Service
 
Metric path (near-real-time, 1-minute):
Atlas metrics ──▶ Atlas Alert Evaluator ──▶ Alert matches ──▶ Notification Service

Alert types:

Type
Example
Evaluation
Threshold
error_count > 100 in 5 minutes
Sliding window counter on Mantis
Rate change
error_rate increased 5x vs 1-hour baseline
Compare current rate to rolling baseline
Anomaly
p99 latency deviates 3 sigma from 7-day rolling average
Statistical model in Atlas
Missing data
no heartbeat from service X for 2 minutes
Dead-man's switch in Alert Evaluator
Pattern match
"OutOfMemoryError" appears in logs
Regex filter on Mantis log stream

Deduplication and routing:

  • Group alerts by rule_id + service + severity
  • Configurable cooldown: alert at most once per 15 minutes per group
  • Escalation chain: Warning -> Slack #alerts-{team} -> if unacknowledged for 10 min -> PagerDuty -> on-call engineer
  • Auto-resolve: if alert condition clears for 5 minutes, send resolution notification

Chaos resilience (monitoring must survive Chaos Monkey):

  • Alert Evaluator runs across 3 AZs with no single points of failure
  • Kafka consumer group rebalancing handles evaluator instance termination
  • Atlas backend uses consistent hashing — loss of one node redistributes time-series to surviving nodes
  • Notification Service has fallback path: if PagerDuty API is down, fall back to direct SMS via AWS SNS
  • "Watchdog" alert: a synthetic heartbeat metric that should always fire. If the watchdog alert stops firing, the alerting system itself is broken — monitored by an independent, minimal system running in a separate AWS account
7

Bottlenecks & tradeoffs

Bottlenecks

1. Elasticsearch indexing throughput: 10M events/sec at 500B each = 5 GB/s ingestion

  • Mitigation: Sampling reduces actual indexing to ~1-2 GB/s; bulk indexing with 5000-doc batches; index-per-service-per-day limits shard count per index

1. Kafka throughput for log topics: Single Kafka cluster handling 5 GB/s of log data

  • Mitigation: Dedicated logging Kafka cluster separate from application Kafka; 100+ partitions for high-throughput services; compression (lz4) reduces network bandwidth

1. Trace storage write amplification: 5M spans/sec to Cassandra

  • Mitigation: Trace sampling at 10% of requests (keep 100% for errored requests); Cassandra's write-optimized LSM architecture handles high write throughput; TTL auto-deletes old traces

1. Hot service log volume skew: A few services (e.g., Zuul, API gateway) produce disproportionate log volume

  • Mitigation: Per-service Kafka topics prevent noisy-neighbor; per-service ES indices allow independent scaling; more aggressive sampling for high-volume services

1. Alert storm during outages: A cascading failure triggers thousands of alerts simultaneously

  • Mitigation: Alert grouping and deduplication; top-level "service down" alerts suppress downstream alerts; rate-limiting notification delivery

Key Tradeoffs

Decision
Option A
Option B
Choice & Rationale
Log search engine
Elasticsearch (full-text, flexible queries)
ClickHouse (columnar SQL, better for analytics)
ES — full-text search on unstructured log messages is the primary use case during incidents
Ingestion buffer
Kafka (durable, replayable)
Direct to ES (simpler, lower latency)
Kafka — decouples ingestion from indexing; enables replay; absorbs ES slowdowns without dropping logs
Metrics DB
Prometheus (open-source, ecosystem)
Atlas (Netflix custom, dimensional)
Atlas — purpose-built for Netflix's 2B+ data points/sec scale; already integrated across 700+ services
Trace storage
Elasticsearch (search-friendly)
Cassandra (write-optimized)
Cassandra — trace queries are always by trace_id (partition key lookup); write throughput matters more than ad-hoc search
Log sampling
None (store everything)
Severity-based configurable
Severity-based — 10x cost reduction; ERROR/FATAL always retained; dynamic upsampling during incidents preserves debugging capability
Storage tiering
All in ES (simpler)
Hot/warm/cold (ES + S3)
Tiered — 90-day retention in ES alone would cost 10x more than S3-based cold tier

Monitoring & Alerting (monitor the monitoring system)

  • Kafka consumer lag: Alert if log indexer falls behind > 5 minutes (log freshness SLA violated)
  • ES cluster health: RED = potential data loss, immediate page; YELLOW = reduced redundancy, warning
  • ES indexing rate: Sudden drop = agent failure or network partition; sudden spike = incident in progress
  • ES search latency p99: Alert if > 10 seconds (dashboard experience degraded)
  • Atlas publish errors: Alert if any service fails to publish metrics (silent monitoring gap)
  • Alert firing rate: Sudden spike = real incident; sustained high = alert fatigue, review thresholds
  • Trace completeness: Percentage of requests with full trace chain — alert if < 90% (indicates broken propagation)
  • Watchdog alert: Synthetic always-firing alert; if it stops, the alerting pipeline itself is broken

Failure Modes

  • Kafka broker failure: Replication factor 3 ensures no data loss; producers retry to other brokers; consumer group rebalances automatically
  • Elasticsearch node failure: Replica shards on surviving nodes serve reads; cluster auto-recovers when node returns; shard allocation throttling prevents recovery storm
  • Atlas backend node failure: Consistent hashing redistributes time-series; in-memory data for failed node lost (acceptable: metrics are re-published every minute)
  • Cassandra node failure: Replication factor 3 with LOCAL_QUORUM reads/writes ensures availability; hinted handoff repairs data when node returns
  • Full AZ outage: All components (Kafka, ES, Atlas, Cassandra) deployed across 3 AZs; survives loss of any one AZ with degraded capacity; auto-scaling replaces capacity in surviving AZs
  • Alerting pipeline failure: Watchdog detection triggers fallback alerting from independent system in separate AWS account; Kafka retains unprocessed events for replay after recovery
  • Chaos Monkey kills monitoring instance: All monitoring components are stateless or replicated; instance termination triggers auto-scaling group replacement; no single instance is critical

Rubric — Senior vs Staff

Dimension
Senior signal
Staff signal
Storage strategy
Keeps all logs in a single search cluster.
Splits hot/warm/cold tiers; justifies the object-store cold tier on ~10x cost.
Ingestion pipeline
Agents ship logs straight into the index.
Durable log buffer decouples ingest from indexing and enables replay and back-pressure.
Volume control
Stores everything or drops whole severity levels.
Severity-based sampling with automatic upsampling for a service during incidents.
Datastore selection
Uses one database for logs, metrics, and traces.
Matches store to workload: search index, dimensional TSDB, write-optimized trace store.
Resilience
Relies on multi-AZ replication.
Self-monitoring with a watchdog and out-of-band fallback; survives random instance termination.
★ MORE WALKTHROUGHS

Want more breakdowns like this?

Join free early access for upcoming RAG, LLM eval, agents, and AI infrastructure walkthroughs.

Join Free Early Access →