Chapter
Distributed Systems
CAP, consistency models, messaging, idempotency, and protecting services under failure.
CAP & PACELC
CAP theorem: in a partition, a distributed system must choose Consistency (every read returns the latest write or errors) or Availability (every request gets a response, possibly stale). Partition tolerance is non-negotiable in real networks — so the tradeoff is really C vs A during failure.
| Choice | During partition | Example systems |
|---|---|---|
| CP | Reject writes/reads to preserve consistency | HBase, MongoDB (default), ZooKeeper |
| AP | Serve requests; replicas may diverge | Cassandra, DynamoDB, CouchDB |
| CA | Only works without partitions (single node) | Traditional RDBMS on one server |
PACELC extends CAP: else (normal operation, no partition), choose Latency vs Consistency. Dynamo-style systems pick EL (low latency, eventual consistency); Spanner picks EC (consistent but higher latency).
How to use CAP in interviews
Don't recite the theorem — apply it: 'During a network split between US and EU, do we block checkout (CP) or allow orders with later reconciliation (AP)?' Tie to business requirements.
Consistency Models
Consistency models define what guarantees readers observe about writes. Stronger guarantees simplify application logic but cost latency and availability. Weaker models scale better but require careful client handling.
| Model | Behavior | Example use |
|---|---|---|
| Linearizable | All ops appear in a single global order | Leader election, distributed locks |
| Strong / sequential | Reads see latest committed write | Bank transfers, inventory deduction |
| Causal | Causally related ops seen in order | Comment threads, chat ordering |
| Read-your-writes | User always sees own updates | Profile edits, draft saves |
| Eventual | Replicas converge given no new writes | Like counts, CDN, DNS |
| Monotonic reads | User never sees time go backward | Social feed pagination |
- Leader-based replication enables strong consistency on primary
- Quorum reads/writes tune consistency vs latency in leaderless systems
- Version vectors and conflict-free replicated data types (CRDTs) for merge
- Session tokens route reads to replica that has seen user's writes
Practical default
Strong consistency for money and inventory. Eventual for analytics, counters, and social engagement metrics. Read-your-writes for user-generated content.
Message Queues
Message queues decouple producers from consumers, absorb traffic spikes, and isolate failures. Point-to-point queues deliver each message to one consumer (work distribution). Messages are deleted after acknowledgment.
| System | Model | Strength |
|---|---|---|
| Amazon SQS | Managed queue | Simple, fully managed, at-least-once |
| RabbitMQ | AMQP broker | Routing, exchanges, complex topologies |
| Redis Streams | In-memory log | Low latency; memory-bound |
| ActiveMQ / Service Bus | Enterprise messaging | Transactions, sessions |
API Server ──produce──▶ [ Queue ] ──consume──▶ Worker Pool
│
(buffers spikes;
retries on failure)- Visibility timeout: message hidden while processing; extend if slow
- Dead-letter queue (DLQ) for messages that fail max retries
- Back-pressure: slow consumers cause queue depth to grow — monitor lag
- Ordering: single partition or FIFO queue when order matters
When to introduce a queue
Async work (email, image processing), spike absorption, cross-service decoupling. Don't add a queue to synchronous user-facing paths unless you can accept delay.
Pub/Sub & Event Streams
Publish/subscribe fan-out delivers one message to many subscribers. Log-based event streams (Kafka, Pulsar) retain ordered, replayable history in partitions — enabling event sourcing, stream processing, and multiple consumer groups at different offsets.
| Pattern | Delivery | Retention |
|---|---|---|
| Pub/Sub (ephemeral) | Fire-and-forget to subscribers | Messages deleted after ack |
| Event log (Kafka) | Durable partitioned log | Configurable retention (days/forever) |
| Event sourcing | State = fold of all events | Full history for audit/replay |
| CDC stream | DB changes as events | Sync search indexes, caches, warehouses |
Producer → Topic (partitions P0, P1, P2)
│
┌─────────┼─────────┐
▼ ▼ ▼
Group A Group B Group C
(analytics)(indexer) (audit)
Each group reads all partitions independently- Partition key determines ordering — same key → same partition
- Consumer group: one consumer per partition for parallel scale
- Replay: reset offset to reprocess history for new downstream
- Schema registry (Avro/Protobuf) for evolution without breaking consumers
Delivery Guarantees
Delivery semantics define whether messages can be lost or duplicated. Exactly-once is the holy grail but expensive; most production systems achieve 'effectively once' via at-least-once delivery plus idempotent consumers.
| Guarantee | Meaning | Consumer requirement |
|---|---|---|
| At-most-once | Fire and forget; may lose messages | Tolerate loss (metrics, logs) |
| At-least-once | Retry until ack; may duplicate | Idempotent processing or dedup |
| Exactly-once | Process once and only once | Transactional outbox, Kafka EOS, or idempotency keys |
- Producer acks: wait for leader (acks=1) or all replicas (acks=all)
- Consumer commit offset after successful processing (not before)
- Transactional producer + read-process-write in one Kafka transaction
- Outbox pattern: write business row + event in same DB transaction
Exactly-once is hard
True exactly-once across services requires distributed transactions or careful idempotency. In interviews, say 'at-least-once with idempotent handlers' unless the problem demands stronger guarantees.
Idempotency & Retries
Idempotent operations produce the same result whether executed once or multiple times. Retries with exponential backoff and jitter are essential in distributed systems where timeouts don't distinguish failure from slow success.
| Pattern | Implementation | Use case |
|---|---|---|
| Idempotency key | Client sends UUID; server dedups in store | Payment POST, order creation |
| Natural idempotency | PUT with same body overwrites | Resource updates by ID |
| Upsert / ON CONFLICT | DB merges duplicate inserts | Event ingestion |
| Dedup table | Track processed message IDs with TTL | Queue consumers |
- Exponential backoff: 1s, 2s, 4s, 8s… cap at max delay
- Full jitter: randomize delay to prevent synchronized retries
- Retry budget: limit total retries to avoid retry storms
- Retry only idempotent operations or those with idempotency keys
Design idempotent payment API
Client sends Idempotency-Key header. Server checks Redis/DB for key → return cached response if exists. Otherwise process, store result with key, TTL 24h. Mention PCI and double-charge prevention.
Rate Limiting & Circuit Breakers
Rate limiting protects services from overload by throttling requests per user, IP, or API key. Circuit breakers stop calling a failing dependency, fail fast, and periodically probe for recovery — preventing cascade failures.
| Algorithm | Mechanism | Burst handling |
|---|---|---|
| Token bucket | Tokens refill at fixed rate; request consumes token | Allows bursts up to bucket size |
| Leaky bucket | Fixed outflow rate; queue or drop excess | Smooth output rate |
| Fixed window | Count requests per time window | Boundary spike (2× at edges) |
| Sliding window log | Timestamp log per request in window | Accurate; more memory |
| Sliding window counter | Weighted previous + current window | Good Redis approximation |
- Circuit states: Closed (normal) → Open (fail fast) → Half-open (probe)
- Open after N failures or error rate threshold in sliding window
- Bulkhead: isolate thread pools per dependency — one slow service can't starve others
- Return 429 Too Many Requests with Retry-After header
CLOSED ──(failures > threshold)──▶ OPEN
▲ │
│ │ (timeout expires)
│ ▼
└──(probe succeeds)──────── HALF-OPEN
(probe fails → OPEN)Distributed rate limiter
Redis INCR with EXPIRE for fixed window. For accuracy use sliding window log in Redis sorted set. Mention per-user vs global limits and rate limit at API gateway edge.
Consensus & Leader Election Intro
Consensus algorithms (Raft, Paxos) let distributed nodes agree on a single value or leader despite failures. Leader election ensures exactly one primary handles writes at a time, enabling strong consistency and coordinated decisions.
| Algorithm | Approach | Used in |
|---|---|---|
| Raft | Leader election + log replication; understandable | etcd, Consul, CockroachDB |
| Paxos | Multi-round voting; theoretically proven | Chubby (Google), early ZK |
| ZAB (ZooKeeper) | Atomic broadcast for coordination | ZooKeeper, Kafka (old controller) |
| Bully / ring | Simple election for homogeneous nodes | Less common in production DBs |
- Raft: follower timeout → candidate → request votes → leader appends log entries
- Quorum (majority) required for election and commit — tolerate f failures with 2f+1 nodes
- Split-brain prevented by requiring majority to hold leadership
- etcd/Consul provide distributed locks, service discovery, and config
Interview depth
You rarely implement Raft — you use etcd, ZooKeeper, or a managed DB with built-in consensus. Explain why leader election matters for 'single writer' consistency and failover.