Distributed Systems Concepts¶
Why This Matters¶
Mid-level and up, every system design interview will probe your understanding of distributed systems. These concepts come up in almost every design - knowing them deeply separates good answers from great ones.
1️⃣ CAP Theorem (Deep Dive)¶
In a distributed system, you can only guarantee 2 of 3: Consistency, Availability, Partition Tolerance.
Since network partitions are unavoidable in real distributed systems, the real choice is CP vs AP.
CP (Consistency + Partition Tolerance):
- All nodes see the same data at the same time
- System may reject requests during partition
- Examples: HBase, Zookeeper, MongoDB (strong consistency mode)
- Use when: Banking, inventory, anything where stale data = wrong
AP (Availability + Partition Tolerance):
- System always responds (may return stale data)
- Nodes may diverge during partition, sync later
- Examples: Cassandra, DynamoDB, CouchDB
- Use when: Social feeds, analytics, anything where eventual consistency is OK
PACELC Extension¶
CAP only covers partition scenarios. PACELC adds the normal case:
If Partition (P):
Choose between Availability (A) or Consistency (C)
Else (E - normal operation):
Choose between Latency (L) or Consistency (C)
Examples:
- DynamoDB: PA/EL (available during partition, low latency normally)
- HBase: PC/EC (consistent during partition, consistent normally)
- Cassandra: PA/EL (tunable consistency)
2️⃣ Consistency Models¶
From strongest to weakest:
Strong Consistency¶
Every read returns the most recent write.
Write(x=5) → Read(x) always returns 5
Used in: Banking, inventory, leader election
Cost: Higher latency (must coordinate across nodes)
Linearizability¶
Operations appear instantaneous and in real-time order.
Most strict form of strong consistency.
Used in: Distributed locks, atomic counters
Example: Zookeeper, etcd
Sequential Consistency¶
All nodes see operations in the same order (not necessarily real-time).
Eventual Consistency¶
Given no new updates, all replicas will eventually converge.
Write(x=5) → some reads may return old value temporarily
Eventually all nodes return x=5
Used in: DNS, social media feeds, shopping carts
Cost: Application must handle stale reads
Read-Your-Writes Consistency¶
After a write, the same client always reads the updated value.
User posts a tweet → they always see their own tweet immediately
Other users may see it later (eventual)
Implementation: Route user's reads to the same replica they wrote to
3️⃣ Replication¶
Leader-Follower (Master-Slave)¶
Architecture:
Leader ──writes──> Follower 1
──writes──> Follower 2
──writes──> Follower 3
Reads: From followers (scale reads)
Writes: Only to leader (single write path)
Pros:
✅ Simple, widely used
✅ Read scaling (add followers)
✅ Failover: promote follower to leader
Cons:
❌ Leader is bottleneck for writes
❌ Replication lag (followers may be behind)
❌ Failover is complex (split-brain risk)
Multi-Leader (Multi-Master)¶
Architecture:
Leader 1 ←──sync──→ Leader 2
(DC East) (DC West)
Pros:
✅ Write to nearest datacenter (low latency)
✅ No single point of failure for writes
Cons:
❌ Write conflicts (both leaders accept conflicting writes)
❌ Complex conflict resolution needed
❌ Hard to implement correctly
Conflict Resolution:
- Last Write Wins (LWW): Use timestamp, latest wins (risk: data loss)
- Merge: Combine both values (works for counters, sets)
- Custom: Application-level resolution
Leaderless (Dynamo-style)¶
Architecture:
Client ──writes──> Any N nodes
Client ──reads──> Any R nodes
Quorum: W + R > N ensures consistency
N=3, W=2, R=2: Strong consistency
N=3, W=1, R=1: High availability, eventual consistency
Examples: Cassandra, DynamoDB, Riak
Pros:
✅ No single point of failure
✅ Highly available
✅ Tunable consistency
Cons:
❌ Complex to reason about
❌ Possible to read stale data
4️⃣ Consensus Algorithms¶
Raft (Easier to understand than Paxos)¶
Used in: etcd, CockroachDB, TiKV
Roles:
- Leader: Handles all client requests
- Follower: Replicates leader's log
- Candidate: Trying to become leader
Leader Election:
1. Follower times out (no heartbeat from leader)
2. Becomes Candidate, increments term, votes for self
3. Sends RequestVote to all nodes
4. If majority votes → becomes Leader
5. Sends heartbeats to prevent new elections
Log Replication:
1. Client sends command to Leader
2. Leader appends to log, sends AppendEntries to followers
3. Once majority acknowledge → commit
4. Leader responds to client
Paxos (Conceptual)¶
Two phases:
Phase 1 (Prepare):
Proposer → "I want to propose, promise not to accept lower proposals"
Acceptors → "OK, I promise" (if higher than seen before)
Phase 2 (Accept):
Proposer → "Accept this value"
Acceptors → "Accepted" (if still promised)
Once majority accept → value is chosen
5️⃣ Distributed Transactions¶
Two-Phase Commit (2PC)¶
Coordinator orchestrates transaction across multiple nodes.
Phase 1 (Prepare):
Coordinator → "Can you commit?" → All participants
Participants → "Yes" or "No"
Phase 2 (Commit/Abort):
If all Yes → Coordinator → "Commit" → All participants
If any No → Coordinator → "Abort" → All participants
Problems:
❌ Blocking: If coordinator crashes after Phase 1, participants wait forever
❌ Single point of failure (coordinator)
❌ Slow (2 round trips)
Saga Pattern (Preferred for microservices)¶
Break transaction into local transactions with compensating actions.
Example: Book flight + hotel + car
1. Book flight (success) → emit FlightBooked event
2. Book hotel (success) → emit HotelBooked event
3. Book car (FAIL) → emit CarBookingFailed event
4. Compensate: Cancel hotel → emit HotelCancelled
5. Compensate: Cancel flight → emit FlightCancelled
Two styles:
Choreography: Each service listens to events, acts, emits next event
Pros: Decoupled, simple
Cons: Hard to track overall state
Orchestration: Central saga orchestrator tells each service what to do
Pros: Clear flow, easy to monitor
Cons: Orchestrator is a dependency
6️⃣ Distributed Caching¶
Caching strategies (cache-aside, write-through, write-behind), eviction policies (LRU/LFU/TTL), cache stampede, hot keys, and consistent hashing for distributing keys across cache nodes are covered in depth in the dedicated component doc:
➡️ Caching
Two distributed-systems points worth remembering here:
- Consistent hashing remaps only ~K/N keys when a node is added/removed (vs hash(key) % N, which remaps almost everything). Virtual nodes even out the distribution.
- Cache + DB consistency is a write-strategy trade-off: write-through (consistent, slower writes) vs write-behind (fast writes, risk of loss) vs cache-aside + invalidation (simple, brief staleness).
7️⃣ Message Queues & Event Streaming¶
Message Queue vs Event Stream¶
Message Queue (RabbitMQ, SQS):
- Message consumed once, then deleted
- Point-to-point or pub/sub
- Good for: Task queues, job processing
Event Stream (Kafka):
- Events retained for configurable period
- Multiple consumers can read same event
- Ordered within partition
- Good for: Event sourcing, audit logs, stream processing
Kafka Deep Dive¶
Concepts:
Topic: Named stream of events
Partition: Ordered, immutable log (unit of parallelism)
Offset: Position of message in partition
Consumer Group: Set of consumers sharing work
Guarantees:
- At-least-once delivery (default)
- Exactly-once (with transactions, more complex)
- Ordering within partition (not across partitions)
Scaling:
- More partitions = more parallelism
- Consumers in group = partitions / consumers (each consumer gets some partitions)
- Replication factor = fault tolerance
Producer:
- Partition key determines which partition
- Same key → same partition → ordered for that key
# Kafka producer (pseudocode)
producer = KAFKA_PRODUCER(brokers = ["kafka:9092"])
PUBLISH producer TO topic "order-events" WITH
key = orderId # Same order -> same partition -> ordered
value = serialize({ orderId, status: "placed", userId })
# Kafka consumer (pseudocode)
consumer = KAFKA_CONSUMER(groupId = "order-processor")
SUBSCRIBE(consumer, topic = "order-events")
FOR EACH message CONSUMED FROM consumer
event = deserialize(message.value)
processOrder(event)
END FOR
8️⃣ Distributed Locks¶
When You Need Them¶
- Prevent double-booking (hotel rooms, seats)
- Ensure only one instance runs a cron job
- Atomic read-modify-write across services
Redis-Based Lock (Redlock)¶
CLASS DistributedLock
FUNCTION init(store)
this.store = store # distributed key-value store (e.g. Redis)
END FUNCTION
FUNCTION acquire(key, ttlMs)
lockValue = NOW() + "-" + RANDOM()
# Atomic set-if-not-exists with expiry
result = STORE_SET_IF_ABSENT(this.store, "lock:" + key, lockValue, expiryMillis = ttlMs)
IF result == SUCCESS THEN
RETURN lockValue # Lock acquired
END IF
RETURN null # Lock not acquired
END FUNCTION
FUNCTION release(key, lockValue)
# Atomic check-and-delete: only delete if we still own the lock
ATOMIC
IF STORE_GET(this.store, "lock:" + key) == lockValue THEN
STORE_DELETE(this.store, "lock:" + key)
END IF
END ATOMIC
END FUNCTION
END CLASS
# Usage
FUNCTION bookSeat(seatId, userId)
lock = new DistributedLock(store)
lockValue = lock.acquire("seat:" + seatId, 5000) # 5s TTL
IF lockValue IS null THEN
THROW Error("Seat is being booked by someone else")
END IF
TRY
seat = DB_GET_SEAT(seatId)
IF seat.booked THEN THROW Error("Seat already booked")
DB_BOOK_SEAT(seatId, userId)
FINALLY
lock.release("seat:" + seatId, lockValue)
END TRY
END FUNCTION
9️⃣ Failure Patterns & Resilience¶
Circuit Breaker¶
CLASS CircuitBreaker
FUNCTION init(threshold = 5, timeout = 60000)
this.failureCount = 0
this.threshold = threshold # Failures before opening
this.timeout = timeout # Time before trying again
this.state = "CLOSED" # CLOSED, OPEN, HALF_OPEN
this.nextAttempt = NOW()
END FUNCTION
FUNCTION call(fn)
IF this.state == "OPEN" THEN
IF NOW() < this.nextAttempt THEN
THROW Error("Circuit breaker is OPEN")
END IF
this.state = "HALF_OPEN"
END IF
TRY
result = fn()
this.onSuccess()
RETURN result
CATCH err
this.onFailure()
THROW err
END TRY
END FUNCTION
# private
FUNCTION onSuccess()
this.failureCount = 0
this.state = "CLOSED"
END FUNCTION
# private
FUNCTION onFailure()
this.failureCount = this.failureCount + 1
IF this.failureCount >= this.threshold THEN
this.state = "OPEN"
this.nextAttempt = NOW() + this.timeout
END IF
END FUNCTION
END CLASS
# Usage
breaker = new CircuitBreaker(5, 30000)
FUNCTION callPaymentService(data)
RETURN breaker.call(FUNCTION() RETURN paymentAPI.charge(data) END)
END FUNCTION
Retry with Exponential Backoff¶
FUNCTION withRetry(fn, maxRetries = 3, baseDelay = 100)
FOR attempt FROM 0 TO maxRetries
TRY
RETURN fn()
CATCH err
IF attempt == maxRetries THEN THROW err
# Exponential backoff with jitter
delay = baseDelay * (2 ^ attempt) + RANDOM() * 100
SLEEP(delay)
END TRY
END FOR
END FUNCTION
# Usage
result = withRetry(FUNCTION() RETURN externalAPI.getData() END, 3, 200)
# Retries at: 200ms, 400ms, 800ms (+ jitter)
Bulkhead Pattern¶
Isolate failures - don't let one service take down everything.
Example: Separate thread pools for different services
- Payment service: 20 threads
- Notification service: 10 threads
- Analytics service: 5 threads
If analytics is slow → only analytics threads are blocked
Payment and notifications continue working
Observability¶
The Three Pillars¶
Metrics: Numerical measurements over time
- Request rate, error rate, latency (RED metrics)
- CPU, memory, disk (USE metrics)
- Tools: Prometheus + Grafana
Logs: Timestamped records of events
- Structured logging (JSON) > unstructured
- Log levels: DEBUG, INFO, WARN, ERROR
- Tools: ELK Stack (Elasticsearch, Logstash, Kibana)
Traces: End-to-end request journey across services
- Trace ID propagated through all services
- Shows where time is spent
- Tools: Jaeger, Zipkin, AWS X-Ray
Key Metrics to Track¶
RED Method (for services):
Rate: Requests per second
Errors: Error rate (%)
Duration: Latency (P50, P95, P99)
USE Method (for resources):
Utilization: % time resource is busy
Saturation: Queue length
Errors: Error count
SLI/SLO/SLA:
SLI (Service Level Indicator): Actual measurement (e.g., 99.5% uptime)
SLO (Service Level Objective): Target (e.g., 99.9% uptime)
SLA (Service Level Agreement): Contract with penalty (e.g., 99.9% or refund)
Quick Reference¶
| Concept | Key Point | Example |
|---|---|---|
| CAP Theorem | CP vs AP trade-off | MongoDB (CP) vs Cassandra (AP) |
| Eventual Consistency | Replicas converge over time | DNS, social feeds |
| Leader-Follower | Writes to leader, reads from followers | MySQL replication |
| Saga Pattern | Distributed transactions via compensation | Booking systems |
| Consistent Hashing | Minimize remapping when nodes change | Redis cluster |
| Circuit Breaker | Stop calling failing services | Payment gateway |
| Raft | Leader election + log replication | etcd, CockroachDB |