The Useful Settings Between Strong and Eventual
Consistency Models
Strong, eventual, causal, read-your-writes, and monotonic reads/writes — what each guarantees to the user and which systems pick which.
What you'll learn
- Distinguish strong, eventual, and causal consistency
- Explain read-your-writes and monotonic guarantees
- Match a consistency model to a product requirement
CAP said that under a partition you trade consistency for availability — but “consistency” isn’t a binary. Between “every read is perfectly current” and “anything goes, it’ll sort itself out eventually” lies a spectrum of consistency models, each a precise promise about what a reader is allowed to see. Knowing them lets you ask for exactly the guarantee a feature needs and not pay for more.
The two endpoints
Strong consistency (linearizability) — once a write completes, every subsequent read, on any node, sees it. The system behaves as if there’s a single copy of the data. This is the most intuitive model and the most expensive: it demands coordination on every operation, which costs latency (PACELC’s “C”) and, under a partition, availability.
Eventual consistency — if writes stop, all replicas eventually converge to the same value, but in the meantime different reads may see different (stale) values. It’s cheap and highly available, which is why it powers DNS, shopping carts, and most large-scale caches. The catch: “eventually” has no deadline, and the anomalies along the way confuse users if you’re not careful.
Read left to right: weaker guarantees, lower cost, higher availability. Most of the useful models live in the middle — eventual consistency plus a specific guarantee that kills a specific class of bug.
Causal consistency
Operations that are causally related are seen by everyone in the same order; unrelated (concurrent) operations may be seen in any order. The canonical example: a comment and its reply. If everyone must see the question before the answer, you need causal consistency — but two unrelated comments can appear in either order without confusing anyone. It’s much cheaper than strong consistency while still feeling correct to humans, because it preserves cause-and-effect.
The session guarantees
These three are the everyday “make eventual consistency not feel broken” models, usually scoped to a single user’s session:
Read-your-writes — after you write, you read your own change. (You post a comment; you see it immediately, even if others don’t yet.) This is the exact bug from the replication lesson — a stale follower hiding your own write.
Monotonic reads — once you’ve seen a value, you never see an older one on a later read. Without it, refreshing a page can make data appear to travel backward in time as you bounce between replicas at different lag.
Monotonic writes — your writes are applied in the order you issued them. Without it, “set name to A, then set name to B” could land as B-then-A and leave the wrong value.
| Model | Guarantee | Kills the bug… |
|---|---|---|
| Strong | every read is current | all staleness |
| Causal | cause precedes effect for all | reply before question |
| Read-your-writes | you see your own writes | ”my edit vanished” |
| Monotonic reads | reads never go backward | data time-traveling |
| Monotonic writes | your writes apply in order | out-of-order updates |
| Eventual | converges if writes stop | (none — cheapest) |
Which systems pick which
- Strong: Spanner, etcd, ZooKeeper, single-primary Postgres. Used for config, locks, balances, anything where a stale read is a correctness bug.
- Causal: MongoDB causal-consistency sessions, some collaborative systems.
- Eventual + session guarantees: DynamoDB, Cassandra (tunable), most CDNs and caches. Used for feeds, profiles, counts — where “a few seconds stale” is fine.
The decision is per-feature, not per-app: a banking app uses strong consistency for the balance and eventual for the “people also viewed” widget.
The JavaScript angle
In Node you often manufacture read-your-writes in the application layer rather than buying it from the database — the session guarantee becomes a few lines of routing logic, exactly as in the replication lesson but generalized:
// After a write, record the leader's WAL position (LSN) in the session.
// On read, demand the replica has replayed at least that far; otherwise
// fall back to the leader (or wait). LSN is monotonic and replica-comparable.
async function writePost(user, post) {
await leader.query(
'INSERT INTO posts (user_id, body) VALUES ($1, $2)',
[user.id, post.body],
);
const { lsn } = await leader
.query('SELECT pg_current_wal_lsn() AS lsn')
.then((r) => r.rows[0]);
user.session.minLsn = lsn; // remember what you must see
}
async function readPosts(user) {
const need = user.session.minLsn;
if (need) {
// Has the replica replayed up to (or past) our write?
const { caughtUp } = await replica
.query('SELECT pg_last_wal_replay_lsn() >= $1 AS "caughtUp"', [need])
.then((r) => r.rows[0]);
if (!caughtUp) {
return leader.query('SELECT * FROM posts WHERE user_id = $1', [user.id]);
}
}
return replica.query('SELECT * FROM posts WHERE user_id = $1', [user.id]);
} Consistency models tell you what a reader may see. The next lesson zooms in on what a transaction guarantees while it runs — the ACID promises and their BASE counterpoint.