nSkillHub
Toggle Dark/Light/Auto mode Toggle Dark/Light/Auto mode Toggle Dark/Light/Auto mode Back to homepage

Consistency, Availability, and the CAP/PACELC Trade-off

Consistency and availability trade-offs show up in nearly every system design discussion. The theory (CAP, PACELC) is well-known; the practical application — knowing which choice to make for a specific use case — is what separates a design-literate engineer from one who just quotes theorems.


CAP Theorem: The Actual Claim

CAP states that in the presence of a network partition, a distributed system must choose between Consistency (all nodes see the same data at the same time) and Availability (every request receives a response, though it may be stale).

What CAP doesn’t mean:

  • It’s not a binary permanent choice — modern systems tune per-operation consistency
  • “Consistent” in CAP means linearizable consistency (strongest form) — not just “data is sometimes accurate”
  • Network partitions are rare but inevitable. The real question is “what do you do when they happen?”

The practical framing: Most distributed systems are not in a constant state of partition. The everyday trade-off isn’t about partitions — it’s about consistency vs latency, which is what PACELC addresses.


PACELC: The More Useful Model

PACELC: During a Partition, choose between Availability and Consistency. Else (normal operation), choose between Latency and Consistency.

The “Else” clause is what matters day-to-day. In normal operation:

  • Consistent reads require coordinating with enough replicas to guarantee the latest write is seen. This takes time.
  • Low-latency reads can return from the nearest replica, which may be slightly behind.

This is the everyday trade-off: do you pay latency for consistency, or accept some staleness for speed?

Database examples:

  • Postgres (single node): PC/EC — consistent, not distributed
  • Cassandra: PA/EL — prefers availability during partition, low latency over consistency in normal operation. Tunable.
  • DynamoDB: PA/EL by default, PA/EC with strong consistent reads option
  • Spanner/CockroachDB: PC/EC — global strong consistency via TrueTime / HLC. You pay the latency.
  • ZooKeeper: PC/EC — consistency over availability

Eventual Consistency: When It’s the Right Choice

Eventual consistency means: if no new updates are made, all replicas will eventually converge to the same value. There’s a window during which replicas may return different values.

Where eventual consistency is fine:

  • Social media feed (10ms of lag between user posts is imperceptible)
  • Product catalog (price changes propagate within seconds — acceptable)
  • User preferences / settings (slight delay in reflecting saved settings is fine)
  • Shopping cart read (showing a slightly stale version on render is fine; write always goes to the authoritative store)
  • View counts, like counts, recommendations

Where eventual consistency is dangerous:

  • Bank balance (two concurrent reads could both show sufficient balance, leading to double-spend)
  • Inventory reservation (two requests could both see 1 item available and both succeed)
  • Authentication tokens (revoked token should not be usable after revocation)
  • Order fulfillment (committing to fulfill an order requires accurate inventory state)

The pattern: eventual consistency is fine for reads of data that isn’t used as a gate on a consequential write. As soon as the read determines whether to allow a write (inventory check → place order), you need a stronger guarantee.


Read-After-Write Consistency

A specific consistency requirement that comes up constantly: after a user writes data, they should see their own write when they read.

The failure mode: User updates their profile picture. They refresh — and see the old picture. The read went to a replica that hasn’t caught up yet. User thinks the save failed; they click save again. Race conditions ensue.

How to achieve it:

  1. Route reads after write to the primary. Simple. Adds latency (primary may be farther away).
  2. Track the write’s replication token and only serve the read from a replica that has caught up to that token. DynamoDB and some Postgres drivers support this.
  3. Read your own writes via the cache. After writing, update the cache. Reads go to cache first. TTL ensures eventual fallback to replica.
  4. Client-side state. Don’t re-fetch after write — update the local state optimistically. User sees their write immediately because the client renders it; the replica discrepancy is irrelevant.

Strong Consistency: When to Pay for It

Strong (linearizable) consistency means a read always returns the most recent committed write. Every reader sees a consistent, global ordering of operations.

When it’s worth the latency and complexity:

  • Financial transactions — account balance, ledger entries
  • Inventory management — decrement stock only if available
  • Distributed locking — only one holder at a time
  • Seat reservations, ticket booking — no double-booking
  • Authentication / authorization state — revoked tokens must not grant access

The implementation question: How do you achieve it? Options:

  • Route to primary — simplest, the primary is authoritative
  • Quorum reads — read from majority of replicas (Cassandra QUORUM, DynamoDB strong reads)
  • Serializable isolation — full serializable transaction isolation in Postgres
  • Optimistic locking — read a version number, write only if version matches, retry on conflict

Bank Balance vs Social Feed: A Contrast

Bank Balance:

  • Reads must be strongly consistent — you’re making a decision (can I withdraw?) based on this read
  • Writes must be atomic and durable
  • Consistency model: serializable transactions on the ledger
  • Availability trade-off: it’s acceptable to return an error rather than a stale balance
  • Implementation: transactions against a single authoritative database; replicas for reporting only

Social Feed:

  • Reads can be eventually consistent — 50ms of lag in feed updates is imperceptible
  • High write throughput (millions of posts/second globally)
  • Consistency model: eventual, with monotonic reads (you don’t see posts disappear after you’ve seen them)
  • Availability trade-off: it’s better to show a slightly stale feed than to return an error
  • Implementation: fan-out on write (push to follower timelines) or fan-out on read (pull and merge), Cassandra or Redis for timeline storage, CDN caching for popular feeds

Explaining CAP to a Product Manager

The honest, non-technical explanation:

“When our database servers can’t talk to each other (a network split), we have a choice: do we keep accepting writes and reads (availability), or do we refuse operations until we know all servers agree on the current data (consistency)?

For most of our features — feed, search, recommendations — it’s fine if different users see slightly different results for a few seconds. We prioritize availability.

For payments and inventory, we cannot show you a balance that’s even 1 cent wrong. We prioritize consistency, and we’ll return an error rather than give you incorrect data.”

Then anchor it to the product: “This is why the checkout flow sometimes shows an ‘out of stock’ error even after you saw 1 item available — the inventory check happened at a different moment, and we’d rather give you a correct error than charge you for something we can’t fulfill.”