~learn.
?

Part 0637 minPreface+60 XP

Part 6: System Design Terminology & Theory.

5 stages14 code

watch it first · newThe whole lesson in a 23-minute animated lecture5 quiz checkpoints · ask the AI about any moment · captions, chapters and speed

Preface · 1 min

Preface#

System design interviews are filled with jargon. Interviewers throw around terms like "CAP theorem," "eventual consistency," "idempotent," and "horizontal scaling" expecting you to not just know what they mean, but to reason about their tradeoffs.

Interviewer: "How would you ensure consistency in this distributed system?"

Weak answer

"I'd use a database that's consistent."

Strong answer

"We need to decide what consistency level we need. For user balances, we need strong consistency — linearizable reads. For the activity feed, eventual consistency is fine — users can tolerate seeing posts a few seconds late. I'd use synchronous replication for the balance table and async replication for the feed, accepting the CAP tradeoff that during a partition, the balance service might become unavailable while the feed remains available with stale data."

This document is your glossary and theory guide — every term you'll encounter in system design, explained with examples and interview context.

ExplainerThe whole lesson: terminology and theory

The trade-offs (CAP during a partition, PACELC otherwise, ACID vs BASE), scaling (up vs out, sharding, consistent hashing that moves about 1/N of keys, quorum replication where R + W > N), data and messaging (cache stampedes, queue vs stream, at-least-once delivery with idempotent consumers, sagas with compensations) and reliability (N+1 redundancy, circuit breakers and a 99.9% SLO's 43.2-minute monthly error budget).

Part I · Stage 1 of 5 · 11 min

Part I: Fundamental Theorems and Trade-offs#

Chapter 1: CAP Theorem#

1.1 The Three Properties#

CAP Theorem (Brewer's Theorem) states that in a distributed system, you can only guarantee TWO of three properties:

C - CONSISTENCY Every read receives the most recent write or an error. All nodes see the same data at the same time.

A - AVAILABILITY Every request receives a response (not an error). The system is always operational.

P - PARTITION TOLERANCE The system continues to operate despite network partitions. Messages between nodes can be lost or delayed.

The Reality: You Don't "Choose 2 of 3"

In distributed systems, network partitions WILL happen. P is not optional — it's a given.

So the real choice is: During a partition, do you want:

  • CP: Consistency (refuse requests until partition heals)
  • AP: Availability (serve requests but may be stale/inconsistent)
Your turn · checkpoint 1not answered

A partition: answer or refuse?

A network partition cuts node B off from node A, and node A has just accepted a write. A client asks node B for data. Node B can refuse until it hears from A, or answer with what it has. Which should it do for a bank balance, and which for a user's profile bio?

Think it through, or jot it down. Then compare.

One strong answer

  • Bank balance: refuse (CP). An error or timeout is better than a wrong balance that leads to money moving twice.
  • Profile bio: answer (AP). A bio that's a few seconds old does no harm, and an error page does.
  • Partitions happen whether you plan for them or not. The real choice is what each kind of data does during one, and different services in one system can choose differently.

1.2 CAP in Practice#

Partition Scenario:

DiagramCAP partition scenario
%% title: CAP partition scenario
flowchart TD
  CW["Client writes<br/>value = 100"]:::client --> A["Node A"]:::core
  CR["Client reads<br/>value = ???"]:::client --> B["Node B"]:::core
  A x--x|"Network Partition"| B

CP System (Consistency + Partition Tolerance): Node B refuses to answer until it can confirm with Node A. Client gets an error or timeout. "Sorry, I can't guarantee this data is current."

Examples: ZooKeeper, etcd, HBase (when configured for consistency), Traditional RDBMS with synchronous replication

AP System (Availability + Partition Tolerance): Node B returns its last known value (might be stale). Client gets a response (possibly outdated). "Here's what I have, but it might not be the latest."

Examples: Cassandra (tunable, default AP), DynamoDB (tunable), CouchDB, DNS

ExplainerCAP during a partition: refuse or answer stale

A network partition cuts Node A off from Node B, and a client writes value = 100 to A. A read at B then either gets an error or timeout (CP: consistent, not available, like ZooKeeper or etcd) or B's last known, stale value (AP: available, not consistent, like Cassandra, DynamoDB or DNS).

CA System (Consistency + Availability): Only possible without partitions — meaning single node! Not relevant for distributed systems.

Examples: Single-node PostgreSQL, Single-node MySQL

1.3 Interview Explanation#

Interview question: "Explain CAP theorem and how it applies to your design."

Strong answer:

"CAP theorem states that in a distributed system experiencing a network partition, you must choose between consistency and availability.

For our user profile service, I'd choose AP — availability over consistency. Here's why:

  1. User experience: Showing slightly stale profile data is better than showing an error.
  2. Nature of data: Profile updates are infrequent. If a user updates their bio and another user sees the old bio for a few seconds, it's acceptable.
  3. Conflict resolution: For concurrent updates, we can use last-write-wins with timestamps, or version vectors for more complex merging.

However, for the payment service, I'd choose CP — we cannot allow inconsistent account balances. Users would rather see an error than have their money disappear or duplicate.

The key is: different parts of the system can make different CAP trade-offs based on their requirements."

Chapter 2: PACELC Theorem#

Your turn · checkpoint 2not answered

Estimate it: a write that waits for every replica

There's no partition. A primary replicates every write synchronously to three followers, which confirm in 2 ms, 3 ms and 40 ms (assumed: the last one is far away). How long does each write wait for replication? What do you trade if the primary stops waiting?

Think it through, or jot it down. Then compare.

One strong answer

  • A synchronous write waits for all confirmations, so it's as slow as the slowest follower: ~40 ms every time.
  • If the primary stops waiting, writes return almost at once, but a read from a follower may miss the latest write.
  • That's a trade with no partition involved: latency against consistency, the "else" half of PACELC.

2.1 Extending CAP#

PACELC: What Happens When There's NO Partition?

CAP only addresses partition scenarios. PACELC extends it:

PACELC
text5 lines
if (Partition) {
    choose: Availability OR Consistency    // Same as CAP
} else {
    choose: Latency OR Consistency         // New insight!
}
Letter Stands for
P Partition
A Availability (partition)
C Consistency (partition)
E Else (no partition)
L Latency (else)
C Consistency (else)
  • Strong consistency requires coordination (adds latency)
  • Low latency requires relaxed consistency

Example: Synchronous replication

  • Write to primary
  • Wait for ALL replicas to confirm
  • Then return success to client

Consistent? Yes. Fast? No — you wait for the slowest replica.

2.2 PACELC Classifications#

System Partition (PA/PC) Else (EL/EC)
DynamoDB PA (Available) EL (Low latency)
Cassandra PA (Available) EL (Low latency)
MongoDB PA (Abadi's classification; majority write concern behaves PC) EC (Consistent)
HBase PC (Consistent) EC (Consistent)
PNUTS (Yahoo) PC (Consistent) EL (Low latency)

PA/EL Systems (DynamoDB, Cassandra):

  • Always try to be fast and available
  • Accept eventual consistency
  • Great for: user sessions, product catalogs, social feeds

PC/EC Systems (HBase; MongoDB with majority reads/writes):

  • Always try to be consistent
  • Accept higher latency and potential unavailability
  • Great for: financial data, inventory counts, user accounts

Chapter 3: ACID Properties#

3.1 The Four Properties#

ACID: Guarantees for Database Transactions

A - ATOMICITY All or nothing. Transaction either fully completes or fully rolls back. No partial updates.

Example: Transfer $100 from Account A to Account B

  • Debit A: $100
  • Credit B: $100 Both happen, or neither happens. Never just one.

C - CONSISTENCY Database moves from one valid state to another. All constraints, triggers, and rules are satisfied.

Example: Account balance cannot be negative

  • If transfer would make balance negative, transaction fails
  • Database never contains invalid data

I - ISOLATION Concurrent transactions don't interfere with each other. Each transaction sees a consistent snapshot.

Example: Two transfers happening simultaneously

  • Transfer 1: A → B
  • Transfer 2: A → C Each sees correct balance, neither corrupts the other.

D - DURABILITY Once committed, data survives crashes. Written to non-volatile storage.

Example: After "COMMIT" returns success

  • Server can crash
  • Power can fail
  • Data is still there after restart

3.2 Isolation Levels#

Isolation Levels (Weakest to Strongest)

1. READ UNCOMMITTED Can read data that hasn't been committed yet.

Problem: Dirty Reads

  • Transaction A updates row (not committed)
  • Transaction B reads updated value
  • Transaction A rolls back
  • Transaction B has garbage data!

Use case: Almost never. Maybe analytics where accuracy doesn't matter.

2. READ COMMITTED Can only read committed data. But same query can return different results within transaction.

Problem: Non-Repeatable Reads

  • Transaction A reads row (value = 100)
  • Transaction B updates row to 200, commits
  • Transaction A reads same row again (value = 200)
  • Different values in same transaction!

Use case: Default in PostgreSQL, Oracle. Good for most applications.

3. REPEATABLE READ Same query always returns same result within transaction. But new rows can appear.

Problem: Phantom Reads

  • Transaction A: SELECT * WHERE price < 100 (returns 10 rows)
  • Transaction B: INSERT row with price = 50, commits
  • Transaction A: SELECT * WHERE price < 100 (returns 11 rows!)
  • New row appeared!

Use case: Default in MySQL. Financial reports within a transaction.

4. SERIALIZABLE Transactions execute as if they were serial (one after another). No anomalies possible.

Implementation: Locking or optimistic concurrency

Use case: Critical financial operations. Highest consistency, lowest throughput.

Comparison Table:

Level Dirty Read Non-Repeatable Phantom
Read Uncommitted Possible Possible Possible
Read Committed No Possible Possible
Repeatable Read No No Possible
Serializable No No No

(These are the ANSI SQL definitions; PostgreSQL's and MySQL InnoDB's snapshot-based Repeatable Read also hide phantoms from plain reads.)

3.3 ACID Implementation#

How Databases Achieve ACID

ATOMICITY: Write-Ahead Logging (WAL)

  1. Write intention to log FIRST
  2. Then modify actual data
  3. On crash: replay log to recover
Transaction starts
  1. Write "BEGIN TX-123" to log
  2. Write "UPDATE accounts SET balance=900 WHERE id=A" to log
  3. Write "UPDATE accounts SET balance=1100 WHERE id=B" to log
  4. Write "COMMIT TX-123" to log
  5. Apply changes to data files

Crash before COMMIT? Transaction never happened.
Crash after COMMIT? Replay log on recovery.

ISOLATION: Locking or MVCC

Locking (Pessimistic):

  • Acquire locks before reading/writing
  • Other transactions wait
  • Simple but can cause contention

MVCC - Multi-Version Concurrency Control (snapshots instead of read locks):

  • Keep multiple versions of each row
  • Each transaction sees a snapshot taken at its start (per statement under Read Committed)
  • Writers don't block readers
  • Used by: PostgreSQL, MySQL InnoDB, Oracle

DURABILITY: fsync to disk

After COMMIT:

  • Flush WAL to disk
  • fsync() ensures data hits persistent storage
  • Only then return success to client

Trade-off: fsync is slow (roughly 10-20ms on a 7,200 RPM HDD)

  • Batch commits (group commit)
  • Use fast storage (NVMe SSD)

Chapter 4: BASE Properties#

4.1 BASE vs ACID#

BASE: The Alternative to ACID

For distributed systems where ACID is too restrictive:

BA - BASICALLY AVAILABLE System guarantees availability (in CAP sense). Might return stale data, but always responds.

S - SOFT STATE State can change over time, even without input. Due to eventual consistency propagation.

E - EVENTUAL CONSISTENCY If no new updates, all replicas will eventually converge. No guarantee of when, but eventually consistent.

ACID vs BASE Comparison:

ACID BASE
Strong consistency Eventual consistency
Pessimistic (lock early) Optimistic (resolve later)
Complex transactions Simple operations
Vertical scaling Horizontal scaling
Traditional RDBMS NoSQL databases

When to Use Each:

ACID (Traditional databases):

  • Financial transactions
  • Inventory management
  • User account management
  • Any place where data integrity is critical

BASE (Distributed databases):

  • Social media feeds
  • Product catalogs
  • Session stores
  • Analytics data
  • Any place where eventual consistency is acceptable

4.2 Eventual Consistency in Practice#

Example: Social Media Post

User posts status update in US data center:

User posts status update in US data center
  • t=0ms —
    User clicks "Post"
  • t=10ms —
    US data center saves post
  • t=10ms —
    User sees their post (read-your-writes)
  • t=50ms —
    Replication to EU data center starts
  • t=150ms —
    EU data center receives post

During t=10ms to t=150ms: US users see the post; EU users don't see the post yet; system is inconsistent but available.

After t=150ms: All users see the post; system is eventually consistent.

Consistency Guarantees (Strongest to Weakest):

  1. STRONG CONSISTENCY - After write completes, all reads see new value.

  2. READ-YOUR-WRITES - You always see your own writes. Others might not see them yet.

  3. MONOTONIC READS - Once you see a value, you never see older values. Time doesn't go backward for you.

  4. MONOTONIC WRITES - Your writes are applied in order. Write A before Write B → A applied before B.

  5. EVENTUAL CONSISTENCY - Eventually, all replicas converge. No timing guarantee.

Implementing Read-Your-Writes:

Option 1: Sticky sessions - Route user to same replica that handled their write.

Option 2: Read from primary - For user's own data, always read from primary.

Option 3: Version tracking - Client tracks version of last write. Only accept reads with version >= last write.

Chapter 5: Consistency Models#

5.1 Linearizability#

Linearizability (a.k.a. atomic consistency; not the same as strict serializability, which adds multi-object transactions)

The strongest consistency model. Operations appear to happen instantaneously at some point between invocation and response.

Every operation has a "linearization point" in real time.

Linearizability: operations over time
Time →
Client A write(x=1)
Client B read(x)
Client C write(x=2)
Client D read(x)
Linearizable execution
  1. write(x=1) completes
  2. read(x) returns 1 (must see the write)
  3. write(x=2) completes
  4. read(x) returns 2

If read(x) started after write(x=1) completed, it MUST return 1 or later value. Never 0.

Where It's Needed:

  • Distributed locks (must see latest lock state)
  • Leader election (must agree on leader)
  • Unique ID generation (no duplicates)

Cost:

  • Requires coordination
  • Higher latency
  • Lower throughput

5.2 Sequential Consistency#

Sequential Consistency

Weaker than linearizability. All operations appear in SOME sequential order. Order is consistent with program order for each process. But doesn't have to match real-time order.

Sequential consistency: valid and invalid orderings
  • Client A: write(x=1), write(x=2)
  • Client B: read(x), read(x)
Ordering Valid?
write(x=1) → write(x=2) → read(x)=2 → read(x)=2 ok
write(x=1) → read(x)=1 → write(x=2) → read(x)=2 ok
read(x)=0 → write(x=1) → write(x=2) → read(x)=2 ok
write(x=1) → write(x=2) → read(x)=2 → read(x)=1 no — No legal order: A wrote 1 before 2, so B can't read 2 and then 1
write(x=2) → write(x=1) → ... no — Violates program order: A wrote 1 before 2

Difference from Linearizability:

  • Linearizable: real-time ordering matters
  • Sequential: only program order matters per process

5.3 Causal Consistency#

Causal Consistency

Operations that are causally related must be seen in order. Concurrent operations can be seen in any order.

Causality: A "happens before" B if:

  1. A and B are in same process, A before B
  2. A is a write, B is a read that sees A
  3. Transitive: A→B and B→C means A→C

Example: Social Media Comments

Causal consistency: social media comments
Who Action Causality
Alice posts "Hello!" (A)
Bob sees Alice's post (causally after A)
Bob comments "Hi Alice!" (B, causally after A)
Carol reads the thread

Carol MUST see: Alice's post before Bob's comment (because B is causally dependent on A).

But two independent comments?

  • Dave: comments "Nice!"
  • Eve: comments "Great!"

Carol might see Dave before Eve, or Eve before Dave. These are concurrent, no causal relationship.

Implementation: Vector Clocks

Each node maintains vector of logical clocks: [Node1_time, Node2_time, Node3_time]

When sending message:

  • Increment own clock
  • Attach vector to message

When receiving:

  • Update vector: max(local[i], received[i]) for each i
  • Increment own clock

Compare vectors to determine causality:

  • V1 < V2 if all V1[i] <= V2[i] and at least one V1[i] < V2[i]
  • If neither V1 < V2 nor V2 < V1, they're concurrent
Quick check4 questions · +5 XP each

CAP, PACELC, ACID and consistency

  1. Q1/4 A network partition cuts node B off from node A. A client asks B for an account balance. What does a CP design do?

    Answer & explanation

    Answer: C

    CP gives up availability during a partition: B refuses rather than risk returning a wrong balance. Returning the last known value is the AP choice, fine for a profile bio but not for money.

  2. Q2/4 There is no partition, but every write waits for all replicas to confirm, so p99 write latency is high. What does switching to async replication buy, and what does it cost?

    Answer & explanation

    Answer: B

    This is PACELC's "else" branch: with no partition you still trade latency against consistency. Partition tolerance is not something you give up by tuning replication; partitions happen regardless.

  3. Q3/4 A report transaction reads a row twice and gets 100, then 200, because another transaction committed in between. What is the weakest isolation level that prevents this?

    Answer & explanation

    Answer: A

    That is a non-repeatable read, which Read Committed allows. Repeatable Read guarantees the same row returns the same value within a transaction. Read Committed only prevents reading uncommitted (dirty) data.

  4. Q4/4 A user edits their bio, refreshes, and sees the old bio because the read hit a lagging replica. Which guarantee is missing, and what is one fix?

    Answer & explanation

    Answer: C

    Read-your-writes means you always see your own updates, even if others briefly do not. Reading your own data from the primary (or tracking versions) provides it. Making every read linearizable would work, but it pays a global latency cost for a per-user problem.

Part II · Stage 2 of 5 · 9 min

Part II: Scaling Concepts#

Chapter 6: Scaling Strategies#

6.1 Vertical vs Horizontal Scaling#

VERTICAL SCALING (Scale Up)

Add more power to existing machine:

  • More CPU cores
  • More RAM
  • Faster disks
  • Better network

Before: 4 cores, 16GB RAM, 500GB HDD After: 64 cores, 512GB RAM, 4TB NVMe

Pros:

  • Simple — no code changes
  • No distributed system complexity
  • ACID transactions easy
  • Lower latency (no network hops)

Cons:

  • Hardware limits (can't buy infinite CPU)
  • Single point of failure
  • Expensive (high-end hardware costs exponentially more)
  • Downtime for upgrades

Use when:

  • Starting out (don't prematurely distribute)
  • Database with complex transactions
  • Workload fits on one machine

HORIZONTAL SCALING (Scale Out)

Add more machines:

Horizontal scaling

Before

Server handles all traffic

After

Load Balancer → S1, S2, S3

Pros:

  • Near-infinite scaling
  • Fault tolerance (one fails, others continue)
  • Cost-effective (commodity hardware)
  • No downtime for scaling

Cons:

  • Distributed system complexity
  • Data consistency challenges
  • Network latency between nodes
  • More operational overhead

Use when:

  • Need high availability
  • Workload exceeds single machine
  • Traffic is unpredictable (auto-scale)

6.2 Stateless vs Stateful Services#

STATELESS SERVICES

Server holds no client-specific state between requests. All state is external (database, cache, client).

DiagramStateless services
%% title: Stateless services
flowchart TD
  R1["Request 1"]:::client --> SA@{ icon: "sd:server", label: "Server A" }
  R2["Request 2"]:::client -->|"any server can handle"| SB@{ icon: "sd:server", label: "Server B" }
  R3["Request 3"]:::client --> SC@{ icon: "sd:server", label: "Server C" }
  SA & SB & SC --> DB@{ icon: "sd:database", label: "Database" }
  class SA,SB,SC core
  class DB store

Characteristics:

  • Any server can handle any request
  • Easy to scale (add more servers)
  • Easy to recover (restart server, no data lost)
  • Load balancer can use round-robin

Examples: REST API servers, Web servers (serving static content), Serverless functions

STATEFUL SERVICES

Server holds state that's needed for subsequent requests.

DiagramStateful services
%% title: Stateful services
flowchart TD
  CA@{ icon: "sd:user", label: "Client A" } ==>|"must go to Server 1!"| S1@{ icon: "sd:server", label: "Server 1<br/>(has A's session)" }
  CB@{ icon: "sd:user", label: "Client B" } --> S2@{ icon: "sd:server", label: "Server 2<br/>(has B's session)" }
  class CA,CB client
  class S1,S2 core

Characteristics:

  • Requests must route to specific server
  • Harder to scale (session affinity needed)
  • Failover is complex (state must be replicated)

Examples: WebSocket servers, Game servers, Database servers

Making Stateful Services Scalable:

Option 1: Externalize state - Store session in Redis/Memcached. Now servers are stateless.

Option 2: Sticky sessions - Load balancer routes same client to same server. Still have failover problems.

Option 3: State replication - Replicate state across servers. Complex but handles failover.

Option 4: Partition state - Each server owns subset of state. Route requests by partition key.

Chapter 7: Load Balancing#

7.1 Load Balancing Algorithms#

1. ROUND ROBIN Requests distributed in order: 1→A, 2→B, 3→C, 4→A, 5→B...

Pros: Simple, even distribution Cons: Ignores server capacity and current load Use: Homogeneous servers, similar request costs

2. WEIGHTED ROUND ROBIN Servers have weights: A(3), B(2), C(1) Distribution: A,A,A,B,B,C,A,A,A,B,B,C...

Pros: Accounts for different server capacities Cons: Static weights, doesn't adapt Use: Mixed hardware environments

3. LEAST CONNECTIONS Send to server with fewest active connections.

Pros: Adapts to actual load Cons: Doesn't account for request complexity Use: Long-lived connections, varying request times

4. WEIGHTED LEAST CONNECTIONS Least connections, but weighted by server capacity. Score = connections / weight Send to lowest score.

Use: Mixed hardware with varying connection times

5. IP HASH Hash client IP to determine server. Same client always goes to same server.

Pros: Session affinity without cookies Cons: Uneven if clients have different request rates Use: Stateful services, caching benefits

6. LEAST RESPONSE TIME Send to server with fastest recent response times.

Pros: Accounts for actual performance Cons: Needs continuous measurement Use: When response time is critical

7. RANDOM Randomly select a server.

Pros: Simple, stateless Cons: Can be uneven short-term Use: Large number of servers (law of large numbers)

7.2 Layer 4 vs Layer 7 Load Balancing#

LAYER 4 (Transport Layer) LOAD BALANCING

Works at TCP/UDP level. Sees: IP addresses, ports Doesn't see: HTTP headers, URLs, cookies

How it works:

  1. Client connects to load balancer IP
  2. LB picks backend server
  3. Forwards TCP connection (NAT or DSR)
  4. All packets in connection go to same server

Pros:

  • Very fast (minimal processing)
  • Protocol agnostic (works with any TCP/UDP)
  • Low latency

Cons:

  • Can't route based on content
  • No SSL termination (typically; AWS NLB can terminate TLS)
  • Limited health checks

Examples: AWS NLB, HAProxy (TCP mode), LVS

LAYER 7 (Application Layer) LOAD BALANCING

Works at HTTP/HTTPS level. Sees: URLs, headers, cookies, body

How it works:

  1. Client connects to load balancer
  2. LB terminates HTTP connection
  3. LB inspects request (URL, headers)
  4. LB opens new connection to chosen backend
  5. Forwards request, returns response

Pros:

  • Content-based routing (URL, header)
  • SSL termination (offload from backends)
  • Request modification (add headers)
  • Better health checks (check HTTP response)
  • Caching, compression, rate limiting

Cons:

  • More CPU intensive
  • Higher latency
  • Must understand protocol

Examples: AWS ALB, nginx, HAProxy (HTTP mode), Envoy

Choosing Between Them:

Use L4 when:

  • Need raw performance
  • Non-HTTP protocols (databases, custom protocols)
  • SSL passthrough required

Use L7 when:

  • Need content-based routing
  • Want SSL termination
  • Need request inspection/modification
  • HTTP-specific features (websocket upgrade, gRPC)

Chapter 8: Sharding and Partitioning#

8.1 Partitioning Strategies#

HORIZONTAL PARTITIONING (Sharding)

Split rows across multiple databases. Each shard has same schema, different data.

Example: Users table with 100M rows

Shard Users
Shard 1 Users 1 - 25M
Shard 2 Users 25M - 50M
Shard 3 Users 50M - 75M
Shard 4 Users 75M - 100M

1. RANGE PARTITIONING

Partition by value ranges.

user_id range Shard
user_id 1-1M → Shard A
user_id 1M-2M → Shard B
user_id 2M-3M → Shard C

Pros:

  • Range queries efficient (users 100-200 on same shard)
  • Easy to understand

Cons:

  • Hot spots (new users always hit latest shard)
  • Uneven distribution if ranges are unequal

2. HASH PARTITIONING

Partition by hash of key.

Worksheetshard = hash(user_id) % num_shards
  • hash(1) % 4 = 1 → Shard B
  • hash(2) % 4 = 2 → Shard C
  • hash(3) % 4 = 3 → Shard D
  • hash(4) % 4 = 0 → Shard A

Pros:

  • Even distribution (if hash is good)
  • No hot spots

Cons:

  • Range queries hit all shards
  • Adding shards requires rehashing (use consistent hashing!)

3. DIRECTORY-BASED PARTITIONING

Lookup table maps keys to shards.

Lookup Service
Key Shard
user_1 → Shard A
user_2 → Shard B
user_3 → Shard A
...

Pros:

  • Flexible placement
  • Can move data easily

Cons:

  • Lookup service is bottleneck/SPOF
  • Extra hop for every query

4. GEOGRAPHIC PARTITIONING

Partition by location for data locality.

Users Data center
US users → US data center
EU users → EU data center
Asia users → Asia data center

Pros:

  • Low latency for users
  • Data sovereignty compliance

Cons:

  • Complex for global data
  • Cross-region queries expensive
Your turn · checkpoint 3not answered

What breaks when a cache cluster grows from 4 to 5 nodes?

A cache cluster places keys with hash(key) % N. Traffic grows, so you add a fifth node to the four. Roughly what share of keys now map to a different node? What does the database behind the cache see in the next few minutes?

Think it through, or jot it down. Then compare.

One strong answer

  • A key stays put only if hash % 4 equals hash % 5, which holds for just 4 of every 20 hash values. So ~80% of keys move.
  • Every moved key is a cache miss on its new node, so most reads fall through to the database at once, right when you were adding capacity.
  • With a hash ring, the new node takes over only the keys between it and its neighbour: about 1/5 move, and the rest stay cached.

8.2 Consistent Hashing#

The Problem with Simple Hashing

Simple: shard = hash(key) % N

Add a server (N=4 → N=5):

WorksheetAdding a server (N=4 → N=5)
  • hash(key) % 4 = 2 → Shard C
  • hash(key) % 5 = 3 → Shard D (different!)

Almost ALL keys move to different shards! Massive data migration, cache invalidation.

Consistent Hashing

Imagine a ring (0 to 2^32):

text9 lines
          0
         /|\
        / | \
       /  |  \
    270   |   90
       \  |  /
        \ | /
         \|/
         180

Place servers on ring (by hashing server name):

  • hash("server-a") = 45 → position 45
  • hash("server-b") = 120 → position 120
  • hash("server-c") = 200 → position 200
  • hash("server-d") = 300 → position 300

Place keys on ring, assign to next server clockwise:

  • hash("user-123") = 100 → server-b (next after 100 is 120)
  • hash("user-456") = 250 → server-d (next after 250 is 300)

Adding a Server

Add server-e at position 150:

Before: Keys 120-200 → server-c After: Keys 120-150 → server-e Keys 150-200 → server-c

Only keys between server-b and server-e move! About 1/N of keys move instead of all keys.

Virtual Nodes

Problem: Servers might be unevenly distributed on ring.

Solution: Each physical server gets multiple positions.

Server Ring positions
server-a → positions 45, 160, 280 (3 virtual nodes)
server-b → positions 90, 200, 320 (3 virtual nodes)

More even distribution, and bigger servers can have more virtual nodes.

Used By:

  • Amazon Dynamo (the 2007 paper behind DynamoDB)
  • Apache Cassandra
  • Memcached clients
  • Content Delivery Networks

Chapter 9: Replication#

9.1 Replication Strategies#

SINGLE-LEADER REPLICATION

One primary (leader), multiple secondaries (followers). Writes go to leader, replicate to followers. Reads can go to any node.

DiagramSingle-leader replication
%% title: Single-leader replication
flowchart TD
  C@{ icon: "sd:user", label: "Client" } -->|"writes"| L
  subgraph RG["reads can go anywhere"]
    L["Leader"]:::core
    F1@{ icon: "sd:database", label: "Follower 1" }
    F2@{ icon: "sd:database", label: "Follower 2" }
    F3@{ icon: "sd:database", label: "Follower 3" }
  end
  L --> F1
  L --> F2
  L --> F3
  class C client
  class F1,F2,F3 service

SYNCHRONOUS REPLICATION

Leader waits for followers to confirm before returning success.

Client → Leader → Followers (wait for ACK) → Success to Client

Pros:

  • Strong consistency
  • No data loss on leader failure

Cons:

  • High latency (wait for slowest follower)
  • Availability impact (follower down = writes blocked)

ASYNCHRONOUS REPLICATION

Leader returns success immediately, replicates in background.

DiagramAsynchronous replication
%% title: Asynchronous replication
flowchart TD
  C["Client"]:::client --> L["Leader"]:::core
  L -->|"Success to Client"| C
  L -.->|"async"| F["Followers"]:::service

Pros:

  • Low latency
  • High availability

Cons:

  • Potential data loss (leader fails before replication)
  • Followers may be behind (replication lag)

SEMI-SYNCHRONOUS REPLICATION

Wait for at least one follower to confirm. Leader confirms after 1+ followers ACK. Other followers replicate async.

Balance between consistency and performance. Used by: MySQL semi-sync replication

9.2 Multi-Leader Replication#

MULTI-LEADER (Multi-Master) REPLICATION

Multiple nodes accept writes. Changes replicate between all leaders.

DiagramMulti-leader replication
%% title: Multi-leader replication
flowchart LR
  LA["Leader A<br/>Reads + writes"]:::core <--> LB["Leader B<br/>Reads + writes"]:::core <--> LC["Leader C<br/>Reads + writes"]:::core

Use cases:

  • Multi-datacenter (leader per datacenter)
  • Offline-capable applications (each device is a leader)
  • Collaborative editing

Conflict Resolution

What if two leaders modify same record simultaneously?

Conflict! Which one wins?
Leader Write
Leader A UPDATE user SET name = 'Alice'
Leader B UPDATE user SET name = 'Bob'

Strategies:

1. LAST-WRITE-WINS (LWW) Timestamp each write, highest timestamp wins.

Simple but can lose data. Time sync issues (clock skew).

2. VERSION VECTORS Track version per node. Detect conflicts, let application resolve.

Neither dominates → conflict!
User Version vector Write
User A [A:1, B:0] name = 'Alice'
User B [A:0, B:1] name = 'Bob'

3. MERGE Combine both values somehow.

For shopping cart: union of items For counters: sum of increments For text: operational transformation

4. CUSTOM RESOLUTION Application-specific logic.

"Most recent login location wins" "Merge address fields, flag for review"

9.3 Leaderless Replication#

LEADERLESS REPLICATION

No single leader. Client writes to multiple nodes. Reads from multiple nodes, use quorum.

WorksheetQuorum: R + W > N

Client writes to N nodes; client reads from N nodes. R + W > N ensures overlap (quorum).

  • N = 3 (total replicas)
  • W = 2 (write to 2 nodes)
  • R = 2 (read from 2 nodes)
  • W + R = 4 > 3 → guaranteed to read latest write

Example: Write x=5, then read

Write x=5, then read
  1. Write x=5 to nodes A and B (W=2)
    • Node A: x=5
    • Node B: x=5
    • Node C: x=1 (stale, missed write)
  2. Read from nodes B and C (R=2)
    • Get x=5 from B, x=1 from C
  3. Return x=5 (highest version)

Quorum Configuration

  • R=1, W=N: Fast reads, slow writes; any one node down blocks writes
  • R=N, W=1: Slow reads, fast writes; any one node down blocks reads, and a write is lost if its only node dies before propagating
  • R=W=N/2+1: Balanced, typical configuration

N=3, R=2, W=2: Good balance N=5, R=3, W=3: More fault tolerant

Used By:

  • Amazon Dynamo (DynamoDB's ancestor; DynamoDB itself uses a Multi-Paxos leader per partition)
  • Apache Cassandra
  • Riak

Sloppy Quorum and Hinted Handoff

If designated node is down, write to another node temporarily. "Hint" stored that data belongs elsewhere. When original node recovers, data transferred back.

Improves availability but weakens consistency guarantee.

Quick check4 questions · +5 XP each

Scaling, sharding and replication

  1. Q1/4 Users are range-partitioned by an auto-increment user_id. Which problem should you expect as the product grows?

    Answer & explanation

    Answer: B

    Range partitioning on a growing key sends all new writes to the latest range: a hot spot. Range queries hitting every shard is the downside of hash partitioning, not range.

  2. Q2/4 You place keys with hash(key) % N and grow from 4 to 5 nodes. Why is consistent hashing better here?

    Answer & explanation

    Answer: C

    With modulo hashing, changing N changes the result for most keys, causing massive migration and cache misses. On a ring, only keys between the new node and its predecessor move. Some data still moves, and even spread needs virtual nodes.

  3. Q3/4 Leaderless store with N = 3 replicas. Which setting guarantees that a read overlaps the latest successful write?

    Answer & explanation

    Answer: A

    The read and write sets must overlap: R + W > N. Here 2 + 2 = 4 > 3. W = 2, R = 1 gives exactly 3, which is not greater than N, so the single node you read may be the one that missed the write.

  4. Q4/4 With async replication, the leader acknowledges a write and then crashes before any follower receives it. A follower is promoted. What happens to that write?

    Answer & explanation

    Answer: C

    Async replication returns success before followers have the data, so a leader crash in that window loses acknowledged writes. Avoiding this is the point of sync or semi-sync replication: wait for at least one follower first.

Part III · Stage 3 of 5 · 6 min

Part III: Data Concepts#

Chapter 10: Data Storage Patterns#

10.1 SQL vs NoSQL#

SQL (Relational) DATABASES

Structured data with fixed schema. Tables, rows, columns. SQL query language. ACID transactions.

Examples: PostgreSQL, MySQL, Oracle, SQL Server

Best for:

  • Complex queries with JOINs
  • Transactions across multiple tables
  • Strong consistency requirements
  • Well-defined schema

NoSQL DATABASES

Various data models, flexible schema. Optimized for specific access patterns.

Types:

1. KEY-VALUE STORES Simple: key → value Examples: Redis, DynamoDB, Memcached Use: Caching, session storage, simple lookups

2. DOCUMENT STORES Key → JSON/BSON document Examples: MongoDB, CouchDB, Firestore Use: Content management, catalogs, user profiles

3. COLUMN-FAMILY (Wide Column) Rows with dynamic columns, column families Examples: Cassandra, HBase, ScyllaDB Use: Time series, IoT, large-scale analytics

4. GRAPH DATABASES Nodes and relationships Examples: Neo4j, Amazon Neptune, JanusGraph Use: Social networks, recommendation, fraud detection

Comparison:

Aspect SQL NoSQL
Schema Fixed, defined Flexible, dynamic
Scaling Vertical (mainly) Horizontal (designed for)
Consistency Strong (ACID) Eventual (BASE) typically
Query Language SQL Varies by database
JOINs Native support Usually application-side
Transactions Multi-table Usually single-document

10.2 Indexing#

INDEX TYPES

1. B-TREE INDEX (Most Common)

Balanced tree structure. Good for: equality, range queries, sorting

Query: WHERE age > 25 AND age < 50

Without index: Scan all rows (O(n)) With B-tree: Navigate tree (O(log n), plus O(k) to scan the k matching rows)

2. HASH INDEX

Hash table structure. Good for: exact equality only

Query: WHERE id = 12345

Hash id → find bucket → get value (O(1))

Cannot do: range queries, sorting

3. INVERTED INDEX (Full-Text Search)

Maps words to documents containing them.

Inverted index
Word Documents
"hello" → [doc1, doc3, doc7]
"world" → [doc1, doc5]

Query: "hello world" → Intersection: [doc1]

Used by: Elasticsearch, PostgreSQL full-text

4. BITMAP INDEX

Bit array for each value. Good for: low-cardinality columns

Value Bit array
gender='M' 1,0,1,1,0,1,0,0...
gender='F' 0,1,0,0,1,0,1,1...

Fast for: COUNT, AND/OR operations

5. GEOSPATIAL INDEX (R-Tree, Quad-tree)

Indexes 2D/3D coordinates.

Query: Find all restaurants within 5km

Used by: PostGIS (R-tree via GiST); MongoDB and Redis use geohash-style cell indexes

COMPOSITE INDEX

Index on multiple columns. Order matters!

INDEX (country, city, street)

Efficient for

  • WHERE country = 'US'
  • WHERE country = 'US' AND city = 'NYC'
  • WHERE country = 'US' AND city = 'NYC' AND street = 'Broadway'

NOT efficient for

  • WHERE city = 'NYC' (leftmost column not used)
  • WHERE country = 'US' AND street = 'Broadway' (gap in columns: only the country prefix is used, street is filtered)

COVERING INDEX

Index contains all columns needed for query. No need to access table data.

Covering index
  1. INDEX (user_id, name, email)
  2. Query: SELECT name, email WHERE user_id = 123
  3. Index has all data → no table lookup → faster!

10.3 Write-Ahead Log and LSM Trees#

WRITE-AHEAD LOG (WAL)

Before modifying data, write to append-only log. Used for durability and crash recovery.

Write path:

  1. Append operation to WAL (sequential write, fast)
  2. fsync WAL to disk
  3. Apply to in-memory data structures
  4. Periodically flush to data files (checkpoint)

Recovery:

  1. Load last checkpoint
  2. Replay WAL from checkpoint
  3. System is consistent

LSM TREE (Log-Structured Merge Tree)

Optimized for write-heavy workloads. Used by: LevelDB, RocksDB, Cassandra, HBase

Structure:

DiagramLSM tree structure
%% title: LSM tree structure
flowchart TD
  MT["MemTable<br/>(in-memory, sorted)"]:::cache -->|"flush when full"| L0[("Level 0 SSTables<br/>(Sorted String Tables on disk)")]:::store
  L0 -->|"compaction merges files"| L1[("Level 1 SSTables")]:::store
  L1 --> LN[("Level N SSTables")]:::store

Write path:

  1. Write to MemTable (memory, fast)
  2. When MemTable full, flush to Level 0 SSTable
  3. Background compaction merges and sorts levels

Read path:

  1. Check MemTable
  2. Check each level (newest to oldest)
  3. Use bloom filters to skip files without key

Pros:

  • Excellent write performance (sequential I/O)
  • Compression friendly

Cons:

  • Read amplification (check multiple levels)
  • Write amplification (compaction rewrites data)
  • Space amplification (data exists in multiple levels)

Chapter 11: Caching Concepts#

Your turn · checkpoint 4not answered

Page-view counters: which cache write pattern?

You count page views. Writes are extremely frequent, and losing a few increments in a crash is acceptable. Would you write each view to the database before replying, or let the cache take it and save it later? Would you make the same choice for account balances?

Think it through, or jot it down. Then compare.

One strong answer

  • Page views: write-behind. The write lands in the cache and returns immediately. A background process saves it to the database later, so the database never sees every single increment.
  • The risk: anything not yet saved is lost if the cache fails. That's fine for view counts.
  • Account balances: no. Use a pattern that writes to the database before replying (write-through, or write to the database and invalidate the cache). Losing a payment isn't acceptable.

11.1 Cache Patterns#

CACHING STRATEGIES

1. CACHE-ASIDE (Lazy Loading)

Application manages cache explicitly.

Read:

python5 lines
data = cache.get(key)
if data is None:
    data = db.query(key)
    cache.set(key, data)
return data

Write:

python2 lines
db.update(key, data)
cache.delete(key)  # Invalidate

Pros: Only requested data is cached Cons: Cache miss penalty, potential stale data

2. READ-THROUGH

Cache sits in front of database. Cache handles misses automatically.

Read:

python1 line
data = cache.get(key)  # Cache fetches from DB on miss

Pros: Simpler application code Cons: Cache library must know how to query DB

3. WRITE-THROUGH

Writes go to cache, cache writes to database. Synchronous — returns after both succeed.

Write:

python1 line
cache.set(key, data)  # Also writes to DB

Pros: Cache always consistent with DB Cons: Higher write latency

4. WRITE-BEHIND (Write-Back)

Write to cache, asynchronously write to database.

Write:

python2 lines
cache.set(key, data)  # Returns immediately
# Background process writes to DB later

Pros: Very fast writes Cons: Data loss risk if cache fails before flush

5. REFRESH-AHEAD

Proactively refresh cache before expiration.

If item accessed and TTL < threshold: Async refresh in background

Pros: Reduces cache miss latency Cons: May refresh unused data

11.2 Cache Eviction Policies#

EVICTION POLICIES

When cache is full, which item to remove?

1. LRU (Least Recently Used)

Remove item that hasn't been accessed longest.

Access order: A, B, C, D, A, B Evict: C (least recently used)

Pros: Good general-purpose policy Cons: One-time scans pollute cache

Used by: Many caches (Memcached); Redis defaults to noeviction unless set to e.g. allkeys-lru

2. LFU (Least Frequently Used)

Remove item with fewest accesses.

Access counts: A(10), B(2), C(5), D(1) Evict: D (least frequent)

Pros: Keeps popular items Cons: Old popular items never evicted

Variant: LFU with aging (decay counts over time)

3. FIFO (First In, First Out)

Remove oldest item.

Insert order: A, B, C, D Evict: A (first inserted)

Pros: Simple to implement Cons: Doesn't consider access patterns

4. TTL (Time To Live)

Items expire after fixed time.

python1 line
cache.set(key, value, ttl=300)  # Expires in 5 minutes

Pros: Bounds staleness Cons: Popular items may expire unnecessarily

5. RANDOM

Randomly select item to evict.

Pros: Simple, no metadata overhead Cons: May evict important items

Real-World Combinations:

  • Redis: Multiple policies (LRU, LFU, TTL, random)
  • Memcached: LRU with slab allocation
  • CPU caches: Pseudo-LRU (approximate for speed)

11.3 Cache Problems#

CACHE PROBLEMS AND SOLUTIONS

1. CACHE STAMPEDE (Thundering Herd)

Problem:

  • Popular key expires
  • 100 requests arrive simultaneously
  • All miss cache, all query database
  • Database overwhelmed

Solutions:

a) Locking: Only one request fetches, others wait

python7 lines
if not cache.get(key):
    if cache.acquire_lock(key):
        data = db.query()
        cache.set(key, data)
        cache.release_lock(key)
    else:
        wait_and_retry()

b) Probabilistic early refresh: Refresh before expiration with some probability. Spreads refresh load over time.

2. CACHE PENETRATION

Problem:

  • Query for non-existent key (e.g., user ID 999999999)
  • Cache always misses
  • Database queried every time
  • Attacker can DoS with fake keys

Solutions:

a) Cache null values:

python2 lines
if db.query(key) is None:
    cache.set(key, NULL, ttl=60)

b) Bloom filter: Check bloom filter before database. If not in bloom filter, definitely doesn't exist.

3. CACHE BREAKDOWN

Problem:

  • Single hot key expires
  • Massive traffic hits database

Solutions: a) Never expire hot keys b) Background refresh before expiration c) Mutex lock (only one request refreshes)

4. HOT KEY

Problem:

  • One key gets disproportionate traffic
  • Single cache node overwhelmed

Solutions:

a) Local caching: Cache hot keys in application memory

b) Key replication: Store same key on multiple nodes key_1, key_2, key_3 all store same data Hash to different nodes

c) Read replicas: Multiple cache replicas for reads

Quick check4 questions · +5 XP each

Indexes, storage engines and caching

  1. Q1/4 A table has INDEX (country, city, street). Which query cannot use it efficiently?

    Answer & explanation

    Answer: B

    Composite indexes are ordered by their leftmost column first. Skipping country means the index cannot narrow the search. Any query that starts from the leftmost column, like country alone, can use it.

  2. Q2/4 Bots request millions of random, non-existent user IDs. Every one misses the cache and hits the database. What fixes this?

    Answer & explanation

    Answer: A

    This is cache penetration: keys that do not exist are never cached, so every request reaches the DB. Caching nulls or a Bloom filter stops them early. TTL and eviction only affect keys that are already cached; these never were.

  3. Q3/4 A very popular cache key expires and 100 requests miss at the same moment, all querying the database. What is the standard defense?

    Answer & explanation

    Answer: C

    This is a cache stampede. A lock or probabilistic early refresh means one query rebuilds the value instead of 100. A shorter TTL makes the expiry, and so the stampede, happen more often.

  4. Q4/4 Why do LSM-tree stores like Cassandra write fast but sometimes check several places on a read?

    Answer & explanation

    Answer: A

    Buffering in memory and flushing sorted files makes writes sequential. The cost is read amplification across levels, which Bloom filters reduce. LSM stores still use a write-ahead log for durability; it is replayed on recovery, not on reads.

Part IV · Stage 4 of 5 · 4 min

Part IV: Messaging Concepts#

Chapter 12: Message Queues and Streams#

12.1 Queue vs Stream#

MESSAGE QUEUE (e.g., RabbitMQ, SQS)

Message consumed by ONE consumer, then deleted.

DiagramMessage queue
%% title: Message queue
flowchart LR
  P["Producer"]:::service --> Q[["Queue"]]:::queue --> C["Consumer"]:::core
  • Messages: A, B, C, D
  • Consumer 1 takes: A, C
  • Consumer 2 takes: B, D
  • After consumption, messages are gone.

Characteristics:

  • Competing consumers (work distribution)
  • At-least-once delivery (with acknowledgment)
  • No replay (message deleted after consume)
  • Good for: Task queues, work distribution

MESSAGE STREAM (e.g., Kafka, Kinesis)

Messages retained, multiple consumers can read.

DiagramMessage stream
%% title: Message stream
flowchart LR
  P["Producer"]:::service --> S[["Stream/Log"]]:::queue
  S --> GA["Consumer Group A"]:::core
  S --> GB["Consumer Group B"]:::core
  • Messages: A, B, C, D (stored with offsets)
  • Consumer Group A: reads A, B, C, D at their pace
  • Consumer Group B: reads A, B, C, D independently
  • Messages retained for configured period.

Characteristics:

  • Multiple consumer groups (fan-out)
  • Replay capability (rewind to past offset)
  • Ordered within partition
  • Good for: Event sourcing, real-time analytics, audit logs

Comparison:

Feature Queue Stream
Consumption Destructive Non-destructive
Consumer groups Competing Independent
Replay No Yes
Ordering Per-queue Per-partition
Retention Until consumed Time/size based
Use case Task distribution Event streaming

12.2 Delivery Guarantees#

DELIVERY SEMANTICS

1. AT-MOST-ONCE

Message may be lost, never delivered twice.

Producer sends → No acknowledgment If lost, gone forever.

Use case: Metrics, logs where loss is acceptable Trade-off: Fastest, least reliable

2. AT-LEAST-ONCE

Message will be delivered, possibly multiple times.

Producer sends → Wait for ACK If no ACK, retry May deliver twice if ACK lost but message received.

Use case: Most applications (with idempotent consumers) Trade-off: Reliable but needs deduplication

3. EXACTLY-ONCE

Message delivered exactly once.

Very hard to achieve in distributed systems!

Approaches: a) Idempotent producer + idempotent consumer Producer retries with same ID Consumer tracks processed IDs, ignores duplicates

b) Transactional messaging Kafka transactions: atomic produce + consume

Use case: Financial transactions, inventory Trade-off: Most complex, some performance cost

Implementing Exactly-Once (Practical):

Producer side (idempotent):

python2 lines
message_id = generate_unique_id()
send(message_id, payload)  # Retry with same ID

Consumer side (idempotent):

python4 lines
if message_id in processed_set:
    return  # Already processed
process(message)
processed_set.add(message_id)  # ideally in the same transaction as process()

With transactional outbox:

sql6 lines
BEGIN TRANSACTION
  INSERT INTO outbox (message)
  UPDATE business_table
COMMIT

-- Separate process reads outbox, publishes to queue

Chapter 13: Event-Driven Architecture#

13.1 Event Sourcing#

EVENT SOURCING

Store events, not current state. Current state = replay all events.

Traditional (State-based):

Account table

id=123, balance=1000, last_updated=2024-01-15

Problems:

  • Lost history of how we got here
  • Can't answer "what was balance on Jan 1?"

Event Sourcing:

WorksheetEvents table
  1. AccountCreated(id=123, initial=0)
  2. Deposited(id=123, amount=500)
  3. Deposited(id=123, amount=700)
  4. Withdrawn(id=123, amount=200)
  • Current balance: 0 + 500 + 700 - 200 = 1000
  • Balance on Jan 1: Replay events up to Jan 1

Benefits:

  • Complete audit trail
  • Time travel (state at any point)
  • Debugging (replay events to reproduce bugs)
  • Event replay (rebuild read models, fix bugs)

Challenges:

  • Event schema evolution
  • Rebuild time for long event streams
  • Snapshots needed for performance

CQRS (Command Query Responsibility Segregation)

Separate write model (events) from read model (projections).

DiagramCQRS
%% title: CQRS
flowchart TD
  CMD["Commands"]:::client --> WM[("Write Model<br/>(Event Store)")]:::store
  WM -->|"project events"| RM[("Read Model<br/>(Optimized for queries)")]:::cache
  QRY["Queries"]:::client --> RM

Write: Append event to event store Read: Query from denormalized read model

Read model can be:

  • Relational database with joins pre-computed
  • Search index (Elasticsearch)
  • Cache (Redis)
  • Multiple different views of same data

13.2 Saga Pattern#

SAGA PATTERN

Manage distributed transactions across services. Instead of ACID, use sequence of local transactions with compensations.

Example: Order Placement

Order placement saga
  1. Order Service: Create order (PENDING)
  2. Payment Service: Charge customer
  3. Inventory Service: Reserve items
  4. Shipping Service: Schedule delivery
  5. Order Service: Mark order (CONFIRMED)

If step 3 fails (out of stock): Compensate step 2: Refund payment · Compensate step 1: Cancel order

Saga Execution Styles

1. CHOREOGRAPHY

Each service listens for events, decides what to do.

DiagramSaga choreography
%% title: Saga choreography
flowchart TD
  E1[["Order Created"]]:::queue -->|"Payment listens"| E2[["Payment Charged event"]]:::queue
  E2 -->|"Inventory listens"| E3[["Items Reserved event"]]:::queue
  E3 -->|"Shipping listens"| E4[["Delivery Scheduled event"]]:::ok

Pros: Decoupled, simple for few steps Cons: Hard to track, distributed logic

2. ORCHESTRATION

Central orchestrator controls the flow.

Orchestrator
  1. Tell Order: Create order
  2. Tell Payment: Charge
  3. Tell Inventory: Reserve
  4. Tell Shipping: Schedule
  5. Tell Order: Confirm

If failure: Execute compensations in reverse order

Pros: Clear flow, easy to track/debug Cons: Orchestrator is potential bottleneck

Saga Compensations

Each action needs a compensation:

Action Compensation
Create order Cancel order
Charge payment Refund payment
Reserve inventory Release inventory
Schedule shipping Cancel shipping

Compensations must be idempotent! May be called multiple times.

Quick check3 questions · +5 XP each

Queues, streams, delivery and sagas

  1. Q1/3 A new fraud-detection service must process every order event from the last 30 days, while the existing billing consumer keeps going. Which fits?

    Answer & explanation

    Answer: B

    Streams keep messages for a retention period, and each consumer group has its own offset, so the new service can replay history. A queue deletes messages once consumed, and competing consumers would split events between billing and fraud.

  2. Q2/3 Your queue gives at-least-once delivery and a consumer charges credit cards. What must the consumer do?

    Answer & explanation

    Answer: B

    At-least-once means a lost ACK causes redelivery, so the same message can arrive twice. Deduplicating by message ID prevents a double charge. At-most-once avoids duplicates by allowing loss, which is worse for payments.

  3. Q3/3 In an order saga, payment succeeds, then inventory reservation fails because the item is out of stock. What happens next?

    Answer & explanation

    Answer: C

    Sagas replace one ACID transaction with local steps plus compensating actions, run in reverse order on failure. There is no cross-service rollback; that is exactly the gap sagas fill. Compensations must also be idempotent, since they may run more than once.

Part V · Stage 5 of 5 · 6 min

Part V: Reliability Concepts#

Chapter 14: Fault Tolerance#

14.1 Failure Types#

FAILURE MODES

1. CRASH FAILURE

Node stops and doesn't respond.

Detection: Heartbeat timeout Recovery: Failover to replica

Example: Process killed, server power off

2. OMISSION FAILURE

Node fails to send or receive messages.

Send omission: Node doesn't send response Receive omission: Node doesn't receive request

Often indistinguishable from crash.

3. TIMING FAILURE

Response outside acceptable time window.

Example: Response takes 30 seconds instead of 100ms

For caller: Looks like failure Challenge: Did request succeed or not?

4. BYZANTINE FAILURE

Node behaves arbitrarily (including maliciously).

Example: Node sends different data to different nodes

Hardest to handle. Requires 3f+1 nodes to tolerate f failures.

Relevant for: Blockchain, hostile environments

Detection Methods:

Heartbeat:

  • Node periodically sends "I'm alive" message.
  • Missing heartbeats → suspected failure.
  • Challenge: Network partition vs actual failure

Timeout:

  • Request times out → suspected failure.
  • Challenge: Slow vs failed

Lease:

  • Node holds lease (timed permission).
  • Must renew before expiration.
  • No renewal → other nodes can take over.
  • Used by: Distributed locks, leader election

14.2 Redundancy Patterns#

REDUNDANCY PATTERNS

1. ACTIVE-PASSIVE (Hot Standby)

Primary handles all traffic. Standby replicates data, takes over on failure.

DiagramActive-passive (hot standby)
%% title: Active-passive (hot standby)
flowchart TD
  T["traffic"]:::client --> P["Primary<br/>(active)"]:::ok
  P <==>|"replication"| S["Standby<br/>(passive)"]:::service

Failover: Promote standby to primary

Pros: Simple, standby has consistent data Cons: Standby resources underutilized

2. ACTIVE-ACTIVE

Multiple nodes handle traffic simultaneously.

DiagramActive-active
%% title: Active-active
flowchart TD
  TA["traffic"]:::client --> A["Node A<br/>(active)"]:::ok
  TB["traffic"]:::client --> B["Node B<br/>(active)"]:::ok
  A <==>|"sync/async"| B

Pros: Better resource utilization, no failover needed Cons: Conflict resolution, data consistency

3. N+1 REDUNDANCY

N nodes handle normal load. +1 spare for failures.

Example: 3 nodes needed for traffic, deploy 4. One can fail without impact.

4. N+2 REDUNDANCY

One failure + one for maintenance.

Example: 3+2 = 5 nodes. Handles one failure while another is being updated.

5. 2N REDUNDANCY

Double everything.

Example: Need 3 nodes, deploy 6. Half can fail without impact.

Used for: Critical systems, disaster recovery

Your turn · checkpoint 5not answered

What breaks when a dependency hangs?

Your service calls a recommendations service, which starts hanging: every call waits the full 30-second timeout and then fails. Your service keeps calling it on every request. What happens to your service? What should it do instead?

Think it through, or jot it down. Then compare.

One strong answer

  • Each request ties up a thread (or connection) for 30 seconds. Soon all of them are waiting on the dead dependency, and your service stops answering even requests that don't need recommendations. The failure has spread upstream.
  • Fail fast instead: after a threshold of failures, stop calling it and return an error or fallback straight away. Every so often, let one test request through to see if it has recovered.
  • That's a circuit breaker. Giving each dependency its own pool (a bulkhead) also keeps one slow dependency from using up every thread.

14.3 Circuit Breaker Pattern#

CIRCUIT BREAKER

Prevent cascading failures by failing fast.

States:

  • CLOSED: Normal operation, requests pass through
  • OPEN: Failure threshold exceeded, requests fail immediately
  • HALF-OPEN: Test if downstream recovered

State Machine:

DiagramCircuit breaker state machine
%% title: Circuit breaker state machine
stateDiagram-v2
  direction LR
  HALF_OPEN: HALF-OPEN
  CLOSED --> OPEN: failures >= threshold
  OPEN --> HALF_OPEN: timeout expires
  HALF_OPEN --> CLOSED: test request succeeds
  HALF_OPEN --> OPEN: test request fails
  class CLOSED ok
  class OPEN bad
  class HALF_OPEN queue

Implementation:

pythonCircuitBreaker · 32 lines
class CircuitBreaker:
    def __init__(self, threshold=5, timeout=30):
        self.failure_count = 0
        self.threshold = threshold
        self.timeout = timeout
        self.state = CLOSED
        self.last_failure_time = None
    
    def call(self, func):
        if self.state == OPEN:
            if time.monotonic() - self.last_failure_time > self.timeout:
                self.state = HALF_OPEN
            else:
                raise CircuitOpenError()
        
        try:
            result = func()
            self.on_success()
            return result
        except Exception as e:
            self.on_failure()
            raise
    
    def on_success(self):
        self.failure_count = 0
        self.state = CLOSED
    
    def on_failure(self):
        self.failure_count += 1
        self.last_failure_time = time.monotonic()
        if self.failure_count >= self.threshold:
            self.state = OPEN

BULKHEAD PATTERN

Isolate failures to prevent spreading. Like ship bulkheads that contain flooding.

DiagramBulkhead pattern
%% title: Bulkhead pattern
flowchart TD
  A["Service A"]:::core --> P1["Pool 1"]:::service --> X["Dependency X"]:::external
  A --> P2["Pool 2"]:::service --> Y["Dependency Y"]:::external

Pool 1 exhausted (X is slow)? Pool 2 still works (Y unaffected).

Implementation:

  • Separate thread pools per dependency
  • Separate connection pools
  • Separate rate limits

Chapter 15: SLIs, SLOs, and SLAs#

15.1 Definitions#

SERVICE LEVEL TERMINOLOGY

SLI (Service Level Indicator) A metric that measures service behavior.

Examples:

  • Request latency (p99 < 100ms)
  • Error rate (errors / total requests)
  • Availability (uptime / total time)
  • Throughput (requests per second)

SLO (Service Level Objective) Target value for an SLI. Internal goal.

Examples:

  • "99.9% of requests complete in < 100ms"
  • "Error rate < 0.1%"
  • "99.95% availability"

SLA (Service Level Agreement) Contract with consequences. External commitment.

Examples:

  • "99.9% uptime or customer gets credit"
  • "Response within 4 hours or penalty"

SLA should be less strict than SLO! (Buffer for unexpected issues)

Relationship:

DiagramSLI, SLO and SLA
%% title: SLI, SLO and SLA
flowchart TD
  SLI["SLI<br/>#quot;Our p99 latency is 85ms#quot;"]:::service -->|"measures"| SLO["SLO<br/>#quot;We aim for p99 < 100ms#quot;"]:::core
  SLO -->|"commits"| SLA["SLA<br/>#quot;We guarantee p99 < 200ms#quot;<br/>(with penalties)"]:::hot

15.2 Error Budgets#

ERROR BUDGET

If SLO is 99.9% availability: Error budget = 0.1% downtime allowed

Per month (30 days): 0.1% × 30 × 24 × 60 = 43.2 minutes of downtime allowed

Error Budget Policy:

  • Budget remaining → Ship fast, take risks
  • Budget exhausted → Focus on reliability, freeze features
Budget at Mode
80% Normal development
50% Increased caution
20% Reliability focus
0% Feature freeze, fix reliability

Calculating Error Budget:

Monthly error budget for 99.9% availability:

WorksheetMonthly error budget for 99.9% availability
  • Total minutes: 30 × 24 × 60 = 43,200 minutes
  • Allowed downtime: 43,200 × 0.001 = 43.2 minutes
  • Incidents this month:
    • Deploy failure: 15 minutes
    • Database issue: 10 minutes
  • Budget used: 25 minutes
  • Budget remaining: 18.2 minutes (42% left)

Monitoring Error Budget:

Track:

  • Current error budget consumption rate
  • Projected budget at end of period
  • Alert when consumption rate too high

If burning budget faster than expected:

  • Investigate
  • Maybe pause risky deployments
  • Focus on reliability
Quick check3 questions · +5 XP each

Fault tolerance and SLOs

  1. Q1/3 Service A calls dependencies X and Y through one shared thread pool. X becomes very slow, and soon calls to Y fail too. What pattern prevents this?

    Answer & explanation

    Answer: A

    Slow calls to X hold every thread in the shared pool, starving Y. Separate pools contain the damage to X's pool. Load balancing and redundancy spread or replace capacity, but the shared pool is still exhausted.

  2. Q2/3 Your SLO is 99.9% monthly availability (43.2 minutes of downtime). You have used 25 minutes, and a 20-minute outage follows. What does the error-budget policy say?

    Answer & explanation

    Answer: B

    25 + 20 = 45 minutes, more than the 43.2-minute budget. At 0% budget the policy is a feature freeze. 18.2 minutes was the balance before the second outage.

  3. Q3/3 You need 3 nodes to carry peak load, and you patch nodes one at a time. You still want to survive a node failure during patching. How many nodes do you deploy?

    Answer & explanation

    Answer: B

    N+2 covers one node down for maintenance plus one unexpected failure while keeping 3 serving. N+1 only covers one of those at a time; 2N works but costs more than this requirement needs.

Summary#

Key Concepts at a Glance#

THEOREMS AND TRADE-OFFS:

CAP Theorem:

  • Consistency, Availability, Partition tolerance
  • During partition: choose C or A
  • CP: refuse requests if can't guarantee consistency
  • AP: serve requests with possibly stale data

PACELC:

  • Extends CAP: what about when no partition?
  • EL: prioritize low latency
  • EC: prioritize consistency

ACID (Databases):

  • Atomicity: all or nothing
  • Consistency: valid state to valid state
  • Isolation: transactions don't interfere
  • Durability: committed = permanent

BASE (Distributed):

  • Basically Available
  • Soft state
  • Eventually consistent

CONSISTENCY MODELS (Strong to Weak):

Linearizability → Sequential → Causal → Eventual

SCALING:

  • Vertical: bigger machine (simple, limited)
  • Horizontal: more machines (complex, unlimited)
  • Stateless: easy to scale, any server works
  • Stateful: needs session affinity or externalized state

SHARDING:

  • Range: predictable, hot spots possible
  • Hash: even distribution, no range queries
  • Consistent hashing: minimal redistribution on changes

REPLICATION:

  • Single-leader: writes to one, reads from many
  • Multi-leader: writes anywhere, conflict resolution needed
  • Leaderless: quorum reads/writes (R + W > N)
  • Sync: consistent but slow
  • Async: fast but possible data loss

CACHING:

  • Patterns: cache-aside, read-through, write-through, write-behind
  • Eviction: LRU, LFU, TTL
  • Problems: stampede, penetration, breakdown, hot keys

MESSAGING:

  • Queue: one consumer, message deleted
  • Stream: multiple consumers, replay possible
  • Delivery: at-most-once, at-least-once, exactly-once
  • Saga: distributed transactions with compensations

RELIABILITY:

  • Circuit breaker: fail fast when downstream is down
  • Bulkhead: isolate failures
  • Redundancy: N+1, N+2, 2N
  • SLI: metric (latency, error rate)
  • SLO: target (99.9% availability)
  • SLA: contract (penalties for breach)
  • Error budget: allowed failure within SLO

Further Reading#

Books#

  • Book

    Designing Data-Intensive Applications by Martin Kleppmann

    • The definitive guide to distributed systems concepts
  • Book

    Database Internals by Alex Petrov

    • Deep dive into storage engines and distributed databases
  • Book

    Site Reliability Engineering by Google

    • SLIs, SLOs, error budgets, and operational practices

Papers#

Online Resources#

Videos#

this lesson in shorts · 3

play them all →

One idea from this lesson in about a minute each, made for your phone.

End of Part 06 · Part 6: System Design Terminology & Theory
Finish Part 06Stages clear as you read them (0/5 so far). Skipping ahead doesn't count: marking complete unlocks after ~7 more min of reading.

Go deeper · reading trail

all reading →

Real write-ups from the teams that built these systems. Each opens on the publisher's site in a new tab.

UC BerkeleyPaper · ~28 min

CAP Twelve Years Later: How the "Rules" Have Changed

Eric Brewer · 2012
What
The CAP theorem's author revisits it twelve years on.
Key idea
'Pick two of three' misleads: partitions are rare, so design for detecting them, operating in a degraded mode and recovering consistency afterwards.
Why here
The authoritative reading of the CAP chapter here, and the bridge to PACELC: the real trade-off is what you give up while partitioned.
taught here: CAP theorem
YalePaper · ~25 min

Consistency Tradeoffs in Modern Distributed Database System Design

Daniel J. Abadi · 2012
What
The paper that introduced PACELC as a refinement of CAP.
Key idea
If there is a Partition choose Availability or Consistency, Else choose Latency or Consistency; most systems pay the latency trade-off all the time.
Why here
The original source for the PACELC chapter, with Dynamo, Cassandra, PNUTS and others classified.
taught here: PACELC theorem
Martin KleppmannBlog post · ~16 min

Please stop calling databases CP or AP

Martin Kleppmann · 2015
What
A critique of labelling real databases as CP or AP.
Key idea
CAP's definitions are narrow (linearizability, total availability), so most systems are neither; talk about specific consistency models and latency instead.
Why here
Sharpens the CAP and consistency-model chapters: in interviews, name the guarantee (linearizable, causal, read-your-writes), not the letter pair.
taught here: Consistency models

New lessons by email

New lessons, side quests and case files as they ship, at most once a week. No spam, unsubscribe anytime.

Discussion · …

Public: everyone reading this lesson sees comments. Be kind and keep it on the material.

Sign in to ask a question or answer one. Reading stays open to everyone.
loading the discussion…