AlgoInsight

Free lesson · System Design Crash Course

Consistency: The Spectrum (Preview)

Easy11 min readsystem-designhlddistributed-systems

Lesson 2.4 - Consistency: The Spectrum (Preview)

The C-word lesson.

Here's a confusing thing about software vocabulary: the word "consistency" means different things in different contexts. In a casual conversation, "consistent" means "the same everywhere" or "doesn't change unexpectedly." In ACID transactions (the C in ACID), it means "transactions preserve database invariants." In distributed systems and the CAP theorem, it means something more specific - and it's that meaning we're going to focus on in this lesson.

Even within distributed systems, "consistency" isn't one thing. It's a spectrum. At one end, "strong" consistency makes the system behave as if there's only one copy of data, even when there are many - slow but predictable. At the other end, "eventual" consistency lets replicas disagree temporarily, with the promise that they'll converge eventually - fast but with quirks. There are several useful points along the spectrum.

Choosing where on the spectrum your system sits is one of the biggest decisions in distributed system design. Pick too strong and you pay in latency and availability. Pick too weak and your application logic gets harder, and bugs that wouldn't have happened with stronger consistency start showing up.

This lesson is a preview - we'll come back to consistency in much more detail in Module 19 (Distributed System Fundamentals) and Module 21 (Coordination and Consensus). For now, the goal is to give you a working mental model: what the spectrum looks like, where common databases sit on it, and why anyone would choose weak consistency.

The Setup: Why Consistency Is Even a Question

In a single-machine database - one server, one disk, no network - consistency is trivial. There's one copy of the data. When you write, the next read sees the write. Done.

The problem starts when you have multiple copies of data on different machines. And in any real system at any meaningful scale, you do. You have a primary database and a read replica. You have caches in front of databases. You have data spread across multiple geographic regions. You have services keeping local copies of frequently-accessed data.

Each of these copies might be slightly out of date. Updating all of them takes time. Network latency, machine failures, congestion - all of these mean the copies are usually slightly different from each other at any given moment.

Now the question becomes: when a client reads, which version do they see? And: if two clients read at the same time but from different copies, do they see the same thing? The answer depends on what guarantees the system provides - its consistency model.

flowchart LR
    Client["Client writes<br/>X = 5"]
    Primary["Primary DB<br/>X = 5"]
    Replica1["Replica 1<br/>X = 5 (updated)"]
    Replica2["Replica 2<br/>X = 4 (not yet)"]
    Replica3["Replica 3<br/>X = 5 (updated)"]

    Reader1["Reader 1<br/>(sees 5)"]
    Reader2["Reader 2<br/>(sees 4 ❓)"]

    Client --> Primary
    Primary -->|"replicate"| Replica1
    Primary -->|"replicate (slow)"| Replica2
    Primary -->|"replicate"| Replica3

    Replica1 --> Reader1
    Replica2 --> Reader2

    style Replica2 fill:#fb7185,stroke:#f43f5e,color:#000
    style Reader2 fill:#fb7185,stroke:#f43f5e,color:#000
A diagram illustrating the basic consistency challenge. A client writes the value 5 to the primary database. The primary then replicates to three replicas. Two of them  - Replica 1 and Replica 3  - have received the update and now show X equals 5. But Replica 2 hasn't received the update yet; it still shows X equals 4. Now two different readers query the system. Reader 1, hitting Replica 1, sees the new value 5. Reader 2, hitting Replica 2, sees the stale value 4. They see different values at the same moment. This is the consistency problem in distributed systems. The system has to decide how to handle this  - block reads until all replicas are updated, allow stale reads, or some compromise in between. The choice is the consistency model.

The consistency model tells you what reads are allowed to return. Different models make different trade-offs.

The Spectrum, From Strongest to Weakest

I'll walk through the most common consistency models in order from strongest (most guarantees) to weakest (fewest guarantees). For each, I'll give the plain-English description, then a use case where it matters.

1. Linearizable Consistency (Strongest)

Linearizable consistency means the system behaves as if there's a single copy of the data, and all operations happen instantly at some point between when they start and when they complete. Reads always see the latest committed write.

Plain English: the system pretends to be a single machine, even though it isn't.

If you write X = 5 and then immediately read X, you'll see 5. If anyone reads X after your write completes, they'll see 5 (or whatever someone else wrote later). There's no scenario where two readers see different "current" values.

This is the strongest consistency model you can get for arbitrary operations. It's also called "atomic consistency" or "strict consistency."

Use cases: Bank accounts. Inventory counts where overselling is bad. Anything where the correctness depends on always seeing the latest value.

Cost: High latency (every operation requires coordination), reduced availability during failures.

2. Sequential Consistency

Sequential consistency is slightly weaker. It says: there's a global ordering of all operations that all clients agree on, but it doesn't have to match real time.

In other words, all clients see operations happen in the same order, but the order isn't necessarily the wall-clock order. Two operations that happened at the same real time can be ordered differently by the system as long as everyone agrees on the order.

This is mostly an academic distinction from linearizable for application developers. The practical difference is subtle. Mention it for completeness; most engineers won't encounter the difference in practice.

3. Causal Consistency

Causal consistency is weaker still. It says: causally related operations are seen in the right order. Concurrent operations can be seen in different orders by different observers.

Plain English: if event B happened because of event A (A caused B), then everyone sees A before B. But if events A and C happened independently, different observers might see them in different orders.

This is the right model for systems like collaborative editing or social media. Examples:

Causal consistency is a good fit for collaborative systems where you don't need everyone to see things in the same order, but you do need cause-and-effect to be respected.

4. Eventual Consistency

Eventual consistency is much weaker. It says: if no new writes happen, all replicas will eventually converge to the same value. But until they do, replicas can disagree, and reads can return stale data.

Plain English: the system promises that, given time and no further changes, things will settle. It makes no promises about what you see in the meantime.

This is the model most "NoSQL" databases use by default. It's what makes them fast and available - they don't coordinate between replicas synchronously.

Use cases: Social media feeds. Like counts. Notification badges. Anything where "slightly stale" is OK and showing something is better than showing nothing.

Cost: Application developers have to think harder. Bugs around stale reads are real. Sometimes you display "wrong" things briefly. But the performance and availability benefits are huge.

5. No Consistency (Read-Your-Writes, Monotonic Reads, etc.)

Below eventual consistency there are even weaker (and weirder) models. The system might give you no guarantee at all about read order. It might let you read a value you wrote earlier and then "lose" it temporarily.

For practical purposes, when people talk about weak consistency, they usually mean eventual. The truly-no-consistency models are mostly academic curiosities or transient states during system reconfiguration.

There are also "client-centric" consistency models that are useful: read-your-writes (a client always sees their own writes immediately), monotonic reads (a client never sees data going backwards in time), monotonic writes (a client's writes appear in order). These can be added to eventually consistent systems to make them more usable.

flowchart TB
    Strong["Linearizable<br/>(strictest)<br/>Acts like single machine"]
    Seq["Sequential<br/>Total order, but not real-time"]
    Causal["Causal<br/>Cause-effect respected"]
    Eventual["Eventual<br/>Converges, no time guarantee"]
    None["No consistency<br/>(weakest)"]

    Strong -->|"weaker"| Seq
    Seq -->|"weaker"| Causal
    Causal -->|"weaker"| Eventual
    Eventual -->|"weaker"| None

    style Strong fill:#a78bfa,stroke:#8b5cf6,color:#000
    style Seq fill:#818cf8,stroke:#6366f1,color:#000
    style Causal fill:#60a5fa,stroke:#3b82f6,color:#000
    style Eventual fill:#fbbf24,stroke:#f59e0b,color:#000
    style None fill:#fb7185,stroke:#f43f5e,color:#000
A diagram showing the consistency spectrum from strongest to weakest. At the top, linearizable consistency  - the strictest. The system acts as if there's a single machine; reads always see the latest writes. Below that, sequential consistency  - there's a total order all clients agree on, but it doesn't have to match real time. Below that, causal consistency  - if event B was caused by event A, everyone sees A before B, but concurrent events can be ordered differently by different observers. Below that, eventual consistency  - replicas may temporarily disagree, but if writes stop, they'll converge. At the bottom, no consistency  - the weakest, where the system makes no guarantees at all. Stronger consistency is at the top and costs more. Weaker consistency is at the bottom and is cheaper but harder to reason about. The skill is picking the weakest model that meets the requirements.

Real Examples

Where do common systems sit on this spectrum?

Single Postgres database, no replicas: Linearizable (you have one copy, so trivially).

Postgres with synchronous replication: Effectively linearizable for writes, with some nuance for reads.

Postgres with asynchronous read replicas: Eventually consistent for reads to the replicas. The lag is typically milliseconds but can be seconds during heavy load.

Most NoSQL databases by default (DynamoDB, Cassandra, MongoDB): Eventually consistent reads. Most of them offer an option for stronger consistency (DynamoDB has "strongly consistent reads"; Cassandra has tunable consistency per query), but at the cost of higher latency.

Google Spanner: Linearizable across globally distributed data, using GPS clocks and atomic clocks (TrueTime) to keep coordination feasible at planet scale. Famously hard to build; Google described how in a paper. (See Module 21 for more.)

DNS: Very eventually consistent. When you update a DNS record, it propagates to caches around the world over minutes to hours, depending on TTLs. During that window, different clients see different values.

S3: Used to be eventually consistent; became strongly consistent in 2020. (See references for the AWS announcement - a major engineering achievement.)

Most caches: Eventually consistent. The cache might be stale by milliseconds, seconds, or minutes, depending on invalidation strategy.

When you read a database's documentation, look for a "consistency" section. It'll tell you what guarantees the database provides. The vocabulary above is what you're matching against.

Why Anyone Would Choose Weak Consistency

A natural question: if strong consistency is "better" (in the sense of being easier to reason about), why would anyone choose weak?

Three reasons.

Reason 1: Latency

Strong consistency requires coordination. To make sure all replicas agree before a read or write completes, the replicas have to talk to each other. That talking takes time - milliseconds within a data center, tens or hundreds of milliseconds across regions.

Eventual consistency skips that coordination. Writes go to the local replica fast. Reads come from the nearest replica fast. The trade-off: stale reads sometimes.

For a service like Facebook's news feed, where users prefer fast loading over perfectly synchronized like counts, eventual consistency is the right choice.

Reason 2: Availability

Strong consistency requires consensus across multiple replicas. If the network partitions (some replicas can't reach each other), the system has to either (a) refuse to serve requests to maintain consistency, or (b) violate consistency to maintain availability.

This is the heart of the CAP theorem, which we'll cover in Lesson 2.6. In short: strong-consistency systems become unavailable during partitions; weak-consistency systems stay available.

For systems where downtime is more painful than slightly stale data (most user-facing systems), eventual consistency wins.

Reason 3: Scale

Strong consistency typically requires global coordination - every write touches some authoritative source. That source becomes the bottleneck. Eventual consistency lets writes happen locally, replicating in the background. This scales much further.

The largest globally distributed systems (Cassandra clusters, DynamoDB) achieve their scale precisely by accepting eventual consistency. The price is paid in application complexity; the benefit is throughput that wouldn't otherwise be possible.

Where Application Code Gets Harder

Weak consistency has a hidden cost: your application code has to handle inconsistencies that wouldn't exist with strong consistency.

Examples:

Read-your-own-writes problem. User updates their profile. Then immediately loads the profile page. They expect to see the change. With eventual consistency, they might not - the read hit a replica that hadn't received the update yet. Workaround: route reads to the primary, or "read your writes" caching, or wait for write confirmation before refreshing.

The like-count problem. User likes a post. Returns to feed. Counter still shows the old number. Workaround: cache the like-count locally and reconcile in the background.

The duplicate problem. User submits an order. Network blip. They retry. The eventually-consistent system might or might not have already processed the first request. Workaround: idempotency keys.

The disappearing data problem. Someone reads a value. Then a moment later, they read it again - and it's gone (because their first read hit a replica that had stale "deleted" status). Workaround: use a system that provides monotonic-reads, or work around it in code.

The conflict problem. Two users edit the same record concurrently from different replicas. Both writes succeed. How do you resolve the conflict? Last-write-wins? Custom merge logic? CRDTs? This is its own deep topic.

These problems are all solvable, but they require deliberate engineering. Application code with eventual consistency is genuinely harder to write correctly than application code with linearizable consistency. The choice to accept this complexity is what you're trading for performance and availability.

A Practical Decision Framework

When designing a new system, the questions to ask:

1. What's the worst case if a user sees stale data?

2. What's the worst case if writes fail during a network problem?

3. How much latency can you tolerate?

4. How distributed is the system?

For most user-facing web applications, the right default is strong consistency for transactions (orders, payments, account balances) and eventual consistency for everything else (feeds, recommendations, analytics, social interactions). Mix them per use case.

Six Takeaways from This Lesson

  1. Consistency in distributed systems means: when reading, what version do you see? Different from ACID consistency, which is about transaction invariants.

  2. Consistency is a spectrum - linearizable, sequential, causal, eventual, none. Each weaker model trades guarantees for performance and availability.

  3. Linearizable consistency makes the system behave as if there's one copy. Strongest, most expensive.

  4. Eventual consistency promises convergence eventually. Cheapest, fastest, but application code has to handle stale reads and conflicts.

  5. Real systems sit at different points on the spectrum. Postgres (linearizable per database). DynamoDB (eventually consistent by default, optionally strong). Cassandra (tunable). S3 (became strong in 2020).

  6. Pick the weakest model that satisfies your requirements. Stronger consistency costs latency, availability, and scale. Mix consistency levels per use case rather than picking one for the whole system.

Try It

Exercise: Warmup (10 minutes). For three services you use (any websites or apps), think about which operations probably need strong consistency vs which can be eventually consistent. Examples: chat message delivery, post likes, payment processing, profile updates.

Exercise: Core (25 minutes). Visit Jepsen.io and pick one database that Kyle Kingsbury has tested. Read his report. What consistency guarantees did the database claim? What did Jepsen find?

Exercise: Stretch (45 minutes). Read Chapter 9 of "Designing Data-Intensive Applications" by Martin Kleppmann (Consistency and Consensus). This is the canonical treatment. Take notes on the differences between linearizability and causal consistency.

What's Next

Lesson 2.5 turns to durability - what it actually means for data to be "saved." We'll cover the journey of a write through layers of memory, disk, and replicas, and look at what the "11 nines" durability number from S3 actually means.

Back to System Design Crash Course