← back

Fundamentals

Consistency Models

Strong, eventual, causal, and read-your-writes consistency. Understand the spectrum from linearizability to eventual consistency and when each is appropriate.

Consistency Models

When data lives on multiple machines, a fundamental question arises: when one node updates a value, when do other nodes see that update? The answer depends on the consistency model the system implements. Choosing the right consistency model is one of the most impactful decisions in system design — it affects correctness, performance, availability, and user experience.

Why Consistency Models Matter

Imagine you post a photo on social media. You hit "post" and immediately refresh your profile. If you do not see your own photo, that feels broken — even though technically the data might still be propagating to the replica your request hit. Now imagine you transfer money between bank accounts. If the system shows inconsistent balances even briefly, money could be double-spent.

Different use cases demand different guarantees. Consistency models give us a vocabulary to discuss these guarantees precisely.

The Consistency Spectrum

Consistency models form a spectrum from strongest to weakest:

1
2
3
4
5
6
7
Strongest ◄─────────────────────────────────────────► Weakest

Linearizability → Sequential → Causal → Read-your-writes → Eventual
     │                │            │              │               │
  "Behaves as     "All nodes    "Cause       "You see your   "Eventually
   one copy"       agree on      before        own writes"     converges"
                   order"        effect"

Stronger models are easier to reason about but harder to implement efficiently. Weaker models allow better performance and availability but push complexity onto the application developer.

Strong Consistency (Linearizability)

Linearizability is the gold standard of consistency. It guarantees that every operation appears to take effect at a single, instantaneous point in time between its invocation and completion. The system behaves as if there is exactly one copy of the data.

What it means in practice

If client A writes a value and client B reads it after the write completes, client B is guaranteed to see the new value. There is a total order of all operations that is consistent with real time.

1
2
3
4
5
6
# With linearizability, this always works correctly
# Thread A:                    # Thread B:
account.balance = 100          #
# write completes at time T    #
                               # read at time T+1
                               assert account.balance == 100  # Always true

How it works

Linearizability typically requires coordination between nodes on every operation. Common approaches:

  • Single-leader replication with synchronous followers: The leader does not acknowledge a write until all (or a quorum of) followers confirm it.
  • Consensus protocols (Raft, Paxos): Nodes agree on the order of operations through a distributed protocol.
  • Google Spanner's TrueTime: Uses GPS receivers and atomic clocks to assign globally meaningful timestamps, achieving linearizability across data centers.

The cost

Linearizability is expensive. Every read and write may need to contact multiple nodes and wait for acknowledgment. During network partitions, linearizable systems must sacrifice availability (this is the CP choice from CAP). Latency increases, especially across geographic regions.

When to use it

  • Financial transactions (account balances, transfers)
  • Distributed locks and leader election
  • Inventory management (preventing overselling)
  • Any operation where stale data causes correctness violations

Real-world systems

  • Google Spanner: Globally distributed, linearizable database using TrueTime
  • etcd / ZooKeeper: Linearizable key-value stores for coordination
  • CockroachDB: Serializable and linearizable SQL database
  • PostgreSQL (single node): Trivially linearizable — one machine, one copy

Sequential Consistency

Sequential consistency is slightly weaker than linearizability. It guarantees that all nodes see operations in the same order, but this order does not need to correspond to real-time ordering. Operations from a single client are always seen in the order they were issued.

The practical difference: with linearizability, if client A's write finishes before client B's read starts (in wall-clock time), B must see the write. With sequential consistency, B might not — as long as all nodes agree on some total order that is consistent with each client's individual ordering.

1
2
3
4
5
6
7
# Sequential consistency allows this scenario:
# Client A writes x=1, then x=2
# Client B writes x=3

# All nodes might see: x=1, x=3, x=2  (wrong under linearizability)
# But all nodes MUST agree on the same order
# And A's operations must appear in order: x=1 before x=2

Causal Consistency

Causal consistency captures the idea that if operation A could have influenced operation B, then everyone must see A before B. Operations that are not causally related (concurrent operations) can be seen in any order.

What counts as causal?

  • If process P does a write, then a read, the read is causally after the write.
  • If process P reads a value that Q wrote, then P's subsequent operations are causally after Q's write.
  • Causality is transitive: if A causes B, and B causes C, then A causes C.
1
2
3
4
5
6
7
8
9
10
11
# Causal consistency example: social media comments
# Alice posts: "I got the job!"
# Bob reads Alice's post, then comments: "Congratulations!"

# Causal consistency guarantees:
# Anyone who sees Bob's comment MUST also see Alice's post.
# You will never see "Congratulations!" without the post it refers to.

# However, Carol might post an unrelated status update concurrently,
# and different users might see Carol's post and Alice's post in different orders.
# That is fine — they are not causally related.

How it works

Causal consistency is typically implemented using vector clocks or version vectors. Each node maintains a vector of logical timestamps, one per node. When a node performs an operation, it increments its own timestamp. When it receives data from another node, it merges the vectors by taking the component-wise maximum.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# Simplified vector clock
class VectorClock:
    def __init__(self, num_nodes, node_id):
        self.clock = [0] * num_nodes
        self.node_id = node_id

    def increment(self):
        self.clock[self.node_id] += 1

    def merge(self, other_clock):
        for i in range(len(self.clock)):
            self.clock[i] = max(self.clock[i], other_clock[i])
        self.increment()

    def happens_before(self, other):
        """Returns True if self causally precedes other."""
        return (all(s <= o for s, o in zip(self.clock, other.clock))
                and any(s < o for s, o in zip(self.clock, other.clock)))

When to use it

Causal consistency is a sweet spot for many applications. It prevents anomalies that confuse users (seeing a reply without the original message) while allowing much better performance than linearizability. It does not require global coordination — only tracking dependencies.

Read-Your-Writes Consistency

This is a session-level guarantee: after you write a value, you will always see that value (or a more recent one) in subsequent reads within the same session. Other users might still see stale data.

Why it matters

Without read-your-writes, a user could update their profile name, refresh the page, and see the old name. The user thinks the update failed and tries again. This creates a terrible user experience even though the system is technically working correctly.

Implementation approaches

  • Sticky sessions: Route all of a user's requests to the same replica. Simple but limits load balancing.
  • Read from leader after write: After a write, read from the leader for a short window (e.g., 10 seconds), then fall back to replicas.
  • Client-side tracking: The client includes a timestamp of its last write, and the server ensures it reads from a replica that is at least that current.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# Read-your-writes with client-tracked timestamps
class ReadYourWritesClient:
    def __init__(self):
        self.last_write_timestamp = 0

    def write(self, key, value):
        result = self.primary.write(key, value)
        self.last_write_timestamp = result.timestamp
        return result

    def read(self, key):
        # Find a replica that is caught up to our last write
        for replica in self.replicas:
            if replica.replication_position >= self.last_write_timestamp:
                return replica.read(key)
        # Fall back to primary if no replica is caught up
        return self.primary.read(key)

Eventual Consistency

Eventual consistency is the weakest useful guarantee: if no new writes are made to a value, all replicas will eventually converge to the same value. There is no bound on how long "eventually" takes (though in practice it is usually milliseconds to seconds).

Why use it

Eventual consistency enables the highest availability and lowest latency. Writes succeed on a single node and propagate asynchronously. Reads never block waiting for replication. This is the foundation of AP systems.

The challenges

  • Stale reads: A client might read old data for an unpredictable amount of time.
  • Conflict resolution: When concurrent writes happen on different nodes, the system needs a strategy to resolve conflicts — last-write-wins, merge functions, or CRDTs.
  • Application complexity: Developers must write code that tolerates stale and potentially conflicting data.

Real-world systems

  • Amazon DynamoDB (default): Eventually consistent reads are cheaper and faster. You can opt into strongly consistent reads per request.
  • Cassandra: Default consistency level `ONE` is eventually consistent. Higher levels like `QUORUM` provide stronger guarantees.
  • DNS: Record updates propagate eventually based on TTL values.
  • Amazon S3: Eventually consistent for overwrite PUTs and DELETEs (though S3 achieved strong consistency in December 2020).
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Eventually Consistent Replication:

Time ──►
Node A: [x=1]────[x=2]──────────────────[x=2]
                    │                       │
              write x=2              (converged)
                    │                       │
Node B: [x=1]──────┼───[x=1]──[x=2]────[x=2]
                    │      │       │
                  async  stale   receives
                  repl.  read    update
                    │
Node C: [x=1]──────┼──────────[x=1]──[x=2]──[x=2]
                                 │      │
                               stale  receives
                               read   update

Choosing the Right Model

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
┌────────────────────┬──────────────┬─────────────┬──────────────────────┐
│ Model              │ Performance  │ Complexity   │ Best For             │
├────────────────────┼──────────────┼─────────────┼──────────────────────┤
│ Linearizability    │ Lowest       │ Highest      │ Financial data,      │
│                    │              │              │ distributed locks    │
├────────────────────┼──────────────┼─────────────┼──────────────────────┤
│ Sequential         │ Low          │ High         │ Multi-processor      │
│                    │              │              │ memory models        │
├────────────────────┼──────────────┼─────────────┼──────────────────────┤
│ Causal             │ Medium       │ Medium       │ Social media,        │
│                    │              │              │ collaborative apps   │
├────────────────────┼──────────────┼─────────────┼──────────────────────┤
│ Read-your-writes   │ Medium-High  │ Low          │ User-facing apps     │
│                    │              │              │ with profile edits   │
├────────────────────┼──────────────┼─────────────┼──────────────────────┤
│ Eventual           │ Highest      │ Lowest       │ Analytics, caching,  │
│                    │              │              │ DNS, CDN             │
└────────────────────┴──────────────┴─────────────┴──────────────────────┘

Decision framework

  1. Start by asking: what is the cost of stale data? If stale data means money loss or safety risk, you need strong consistency. If it means a user sees a 2-second-old version of a post, eventual consistency is fine.
  1. Consider per-operation consistency. You do not need one model for the entire system. Use strong consistency for writes to account balances and eventual consistency for read-heavy profile data.
  1. Factor in geography. Strong consistency across continents means cross-continental round trips on every operation (50-200ms). If your users are global, eventual consistency for reads can dramatically improve their experience.
  1. Think about conflict resolution. If your data model supports clean merges (counters, sets, append-only logs), eventual consistency with CRDTs can give you both availability and automatic conflict resolution.

Interview Tips

  1. Name the models precisely. Do not just say "consistent" — specify linearizable, causally consistent, or eventually consistent. Precision shows expertise.
  1. Connect models to real systems. "DynamoDB defaults to eventual consistency for reads, but you can request strongly consistent reads at 2x the cost" demonstrates practical knowledge.
  1. Discuss the spectrum, not a binary. Consistency is not "strong or weak." Show that you understand the gradations and can pick the right point for each use case.
  1. Mention the performance implications. Every step up in consistency requires more coordination, which means higher latency and lower throughput. Quantify when possible: "Synchronous replication to three nodes adds roughly 2x the latency of a single write."
  1. Use causal consistency as your secret weapon. Many candidates only know strong vs eventual. Mentioning causal consistency (and why it is often the right choice) stands out.
  1. Tie it back to user experience. "Read-your-writes matters here because without it, a user updates their profile and immediately sees the old version — they will think the app is broken." This shows you think about systems from the user's perspective.