Consistency Models

PACELC: The Trade-off That Exists When Nothing Is Broken

CAP describes one failure. PACELC adds the case you actually live in: if there is a Partition, trade Availability against Consistency — Else, trade Latency against Consistency. The second half is where nearly all your engineering time goes, and CAP is silent about it.

▶ Run the lab

The question this answers

The question

The network is fine, which is almost always. What is the trade-off then?

The guarantee — the property claimed, and its scope

PACELC is a reasoning framework, not a theorem and not a guarantee. It asserts a structure: during a Partition, a system trades Availability against Consistency (this half is CAP); otherwise, it trades Latency against Consistency, because stronger consistency requires more communication before an operation can return. The latency half is an observation about coordination, not an impossibility result.

Everything below is bought to hold this sentence. "Strongly consistent" with no scope attached is a slogan, not a guarantee — read what it actually covers, and what it explicitly does not.

What a node knows — observation versus inference

A node handling an operation knows how many other nodes it must hear from before it can return an answer at the requested consistency level, and it knows how long each of them has been taking. That is a directly measurable quantity — which is why the else-branch of PACELC, unlike the partition branch, shows up on a latency dashboard rather than in an incident report.

A node knows its own state and the messages that arrived. Everything else is inference from evidence that was already stale. "B has not replied in five seconds" is knowledge; "B is down" is a decision — and usually the bug.

What guarantee?What does a node know?How does it work?What can fail?How does it fail?Where is coordination?What holds under failure?How does it recover?How would you know?What is the simpler thing?
PACELClatencyconsistencytrade-offs

The half CAP omits

CAP is a conditional whose antecedent is "there is a partition". Partitions are real but they are rare; the network is healthy the overwhelming majority of the time, and CAP has nothing to say about that period. Yet the consistency decision still costs something then — it costs latency, because an operation at a stronger level must wait for more nodes before returning.

Daniel Abadi's observation was that this second trade-off explains system behaviour far better than CAP does day to day. A globally-distributed database that answers a linearizable read in 180ms and an eventually-consistent read in 4ms is not exhibiting anything CAP describes. It is exhibiting the else-branch, and that branch is what your p99 chart is made of.

BranchConditionThe tradeWhere you see it
PA / PCprotocolA partition is in progressAvailability vs consistencyIncident reports, a few times a year
EL / ECtypicalNormal operationLatency vs consistencyEvery latency dashboard, every day
The two branches, and where each one shows up

Reading the four combinations without over-trusting them

The framework yields four labels — PA/EL, PA/EC, PC/EL, PC/EC — and they are genuinely useful as a way to describe a *configuration*. A leaderless store with low quorum settings is PA/EL: it stays available during a partition and returns fast at the cost of staleness. A consensus-replicated store with linearizable reads is PC/EC: it refuses on the minority side and pays round trips in normal operation. Spanner-class systems are PC/EC and are honest about the latency floor that commit-wait imposes.

But do not treat this as a taxonomy of products. Modern systems are tunable per operation: the same database serves a linearizable read and an eventually-consistent read in the same second, so it is PC/EC for one request and PA/EL for the next. The classification applies to a configuration and an operation, not to a logo. Treating it as a product taxonomy reproduces exactly the error that made "pick two" so damaging.

The other honest limitation: the else-branch is not a theorem. It is a robust empirical regularity with a clear mechanism — coordination requires messages, messages take time — but there is no impossibility proof of the CAP kind behind it. Reason with it as a strong default, not as a law.

  • PA/EL — available under partition, low latency normally, staleness always. Leaderless stores at low quorum.
  • PC/EC — refuses on the minority side, pays coordination normally. Consensus-replicated stores with linearizable reads.
  • PC/EL — consistent under partition, fast normally: possible with leases and local reads, at the cost of assumptions about clocks.
  • PA/EC — available under partition but slow normally: rare and usually a sign of a misconfiguration rather than a design.

Using the else-branch as a design tool

The productive use of PACELC is per operation. For each one, ask both questions and write down both answers. "During a partition: does this operation serve, refuse, or degrade?" and "In normal operation: what does the consistency level I chose cost this operation in latency?" Most systems have never answered the second question explicitly, and it is usually where the cheapest wins are.

The wins come from noticing that the strong level is rarely needed by every operation. A checkout write needs linearizability; the product page read next to it does not. Splitting them is a small change that removes coordination from the high-volume path while keeping it where the invariant lives. This is the same discipline as Coordination Avoidance: Restructuring the Problem Instead of Paying for It, approached from the latency side.

operation                partition -> ?          normal cost of the level
------------------------------------------------------------------------
place order (write)      refuse on minority      quorum write, +12 ms
reserve inventory        refuse on minority      linearizable CAS, +14 ms
read product page        serve stale             local replica, +0 ms
read own order status    serve, session floor    replica or leader, +0-11 ms
recommendations          serve stale             local cache, +0 ms

three of five operations need no coordination at all.
that observation is worth more than any CP/AP label.
The per-operation PACELC table worth actually writing

Key points

  • PACELC adds the else-branch CAP omits: without a partition, consistency trades against latency.
  • The latency branch is where nearly all engineering time goes; the partition branch appears a few times a year.
  • The four labels describe a configuration and an operation, not a product — modern systems are tunable per request.
  • The else-branch is a strong empirical regularity with an obvious mechanism, not a theorem. Do not over-cite it.
  • The useful artefact is a per-operation table with both answers filled in.

The chain, answered

Every field here is required, which is why no lesson in this domain can recommend a design without naming what an operator sees when it fails, what survives the partition, what repairs it afterwards, and the simpler thing to consider first.

How it works
  • For an operation, identify the consistency level requested.
  • Determine how many other nodes must be heard from before the operation can return at that level.
  • Under a partition, that requirement may be unsatisfiable — the system then refuses (PC) or answers anyway at a weaker level (PA).
  • Without a partition, the requirement is satisfiable but costs the round-trip time to the required nodes — that is the EL/EC trade.
  • Because the requirement is per operation, both branches are per operation too, and a single system exhibits several combinations.
What can fail at the boundary
  • A team classifies the whole system as one label and applies it to operations with different needs.
  • The default consistency level is stronger than most operations need, and coordination cost is paid everywhere.
  • The default is weaker than the invariant-bearing operation needs, and correctness is lost silently.
  • Cross-region coordination is enabled without anyone measuring the latency it adds.
  • A degraded mode changes the consistency level silently, so the else-branch cost disappears and so does the guarantee.
How it fails — what an operator sees
  • Uniform over-coordination: every read is linearizable because that was the cluster default, and p99 latency is dominated by quorum round trips for data nobody would notice being stale. Observed as latency that scales with cluster size rather than with load.
  • Uniform under-coordination: everything runs at a fast weak level including the operation guarding an invariant. Observed as rare oversells or duplicate identities, with no latency symptom to point at the cause.
  • Cross-region latency surprise: a consistency level that was cheap in one region becomes an inter-region round trip after a multi-region rollout. Observed as a step change in p99 correlated with a deployment, not with traffic.
  • Invisible level change under failover: the system degrades to a weaker read level during an incident and the guarantee vanishes for the duration. Observed only if the level in force is instrumented, which it usually is not.
  • Label-driven design: a team adopts a "PA/EL system" and then implements an invariant on it, because the label was treated as a property of the product rather than of a configuration.
Where coordination is required
  • The else-branch is a direct measurement of coordination: latency added equals the time to reach the nodes the level requires.
  • That makes coordination cost visible in a way the partition branch never is — you can see it on a chart today rather than waiting for an incident.
  • Across regions the floor is physical and non-negotiable. See The One Number You Cannot Optimise and perfLinks cross-region-latency.
What still holds under failure
  • During a partition the else-branch is irrelevant and CAP's dilemma applies.
  • After heal, latency returns to the else-branch cost, which is why a partition often shows as a latency change rather than an error in weakly-consistent systems.
  • A system that silently weakens its level under stress moves itself along the EL/EC axis without telling anyone.
How it recovers
  • Detect: record the consistency level in force per request, and the coordination latency attributable to it.
  • Contain: set the default level to the weakest that is correct, and raise it explicitly for the operations that need it.
  • Recover: nothing to recover — the else-branch is a steady-state property, not a failure.
  • Reconcile: if the level was silently weakened during an incident, treat that window as one where the invariant may have been violated.
  • Verify: measure both branches deliberately — inject a partition to test the P-branch, and measure per-level latency to test the E-branch.
How you would know
  • Consistency level in force per request, as a first-class dimension on latency metrics.
  • Latency delta between levels for the same operation — the direct price of coordination.
  • Fraction of traffic running at each level, which shows whether the default is doing more work than needed.
  • Cross-region coordination count per request, since that is where the latency floor is physical.
  • Alerts on silent level downgrades during degraded modes.
When it helps
  • Everyday design conversations, where CAP is inapplicable because nothing is broken.
  • Justifying per-operation consistency levels rather than one cluster-wide setting.
  • Explaining a latency regression after a multi-region rollout, where the mechanism is coordination rather than load.
  • Evaluating a managed database by asking about both branches instead of accepting a single label.
When it hurts
  • As a product taxonomy — the same mistake as "pick two", one level up.
  • When cited as a theorem; the else-branch has no impossibility proof behind it and does not need one.
  • For single-region, single-node systems, where neither branch has any content.
Simpler alternatives

The branch CAP omits — and it is the one you are in now

The branch CAP omits — and it is the one you are in right now
If there is a Partition, trade Availability against Consistency. Else, trade Latency against Consistency. The else-branch is where the network is healthy, which is nearly always, and it is where the engineering time goes.
quorum write
W = 3 of 5 (2 peers)
EC: coordinated p99
83 ms
EL: local read
no remote wait
operations choosing EC
0 of 4
operationinvariant it must protectduring a partitionelse (the healthy case)what that costs
Load a product pagenone — a stale price is a business decision, not a correctness one
served locally — no remote wait
answers on both sides, and you reconcile afterwards
Add an item to a cartnone that a single node cannot check — adding only ever adds
served locally — no remote wait
answers on both sides, and you reconcile afterwards
Claim a usernameglobally unique — no two accounts may hold it
served locally — no remote wait
answers on both sides, and you reconcile afterwards
⌐ differs from the invariant’s requirement
Charge a card onceexactly one charge per order id
served locally — no remote wait
answers on both sides, and you reconcile afterwards
⌐ differs from the invariant’s requirement
During a partitionWhen the network is healthyTypical of
PA/ELtypicalAnswers on both sidesServes locally, no waitingDynamo-style stores at low consistency levels; caches
PA/ECtypicalAnswers on both sidesCoordinates when healthy anywayTunable stores configured for quorum reads and writes
PC/ELtypicalRefuses on the minority sideServes locally when healthyLeader-based systems with follower reads
PC/ECtypicalRefuses on the minority sideCoordinates on every operationConsensus-backed systems at their strongest setting
The four combinations — read them as a description of one operation in one configuration, never as a label for a product
2 rows priced against nothing
Look at the rows flagged ⌐. Coordinating an operation whose invariant is “none” buys nothing and costs 83 ms at the tail on every call; serving a uniqueness claim locally is cheap and wrong. The choice is per operation, and both branches have to be answered — “our database is CP” answers neither.
The partition branch shows up in an incident review a few times a year. The else-branch shows up on a latency dashboard every day, because stronger consistency means more machines must be heard from before an operation can return, and that is directly measurable: a node knows how many peers it must wait for and how long each has been taking. Use the else-branch as a design tool — it is a strong empirical regularity with an obvious mechanism rather than an impossibility result, so do not cite it as though it were a theorem.
assumptionPACELC is a reasoning framework, not a theorem: the partition half is CAP, and the latency half is an observation about coordination with an obvious mechanism. The latency figures are a log-normal model of 2 intra-cluster calls at your p50/p99 — the shape is right, the numbers are not a measurement.

What people believe, and what is true

Claim

PACELC is a stronger version of CAP.

Reality

It contains CAP as its first branch and adds a non-theorem second branch. It is broader in scope and weaker in formal status, and both facts matter when you cite it.

Claim

PACELC classifies databases.

Reality

It classifies a configuration for an operation. A tunable system is PC/EC for a linearizable read and PA/EL for an eventual one, in the same second, on the same cluster.

Claim

The else-branch means strong consistency is always slower.

Reality

It means it requires more communication. Where the required nodes are close — same rack, same zone — the added latency can be negligible. The cost scales with distance, which is why it is a multi-region issue above all.

Claim

If we are EL we do not need to think about consistency.

Reality

You still need to name which model you get and check it against your invariants. Choosing low latency is not the same as choosing nothing.

Go deeper

Only the levels this lesson can honestly fill — a missing level is a claim nobody had.

Overview

If Partitioned: availability versus consistency. Else: latency versus consistency. The second case is the one you are in almost always.

Practical

Build the per-operation table: what each operation does during a partition, and what its consistency level costs in normal-operation latency. Set the cluster default to the weakest correct level and raise it explicitly where an invariant lives.

Advanced

The else-branch is the latency face of the same object CAP shows the availability face of: coordination requires messages, and messages cost time when the network works and cost availability when it does not. Reading both branches as one phenomenon is what makes the trade-off feel inevitable rather than arbitrary — and it is why Coordination Avoidance: Restructuring the Problem Instead of Paying for It pays off twice, improving p99 in the common case and shrinking the unavailable surface in the rare one. See Coordination Couples Availability.

Apply it

Interview questions
  • 💬 CAP describes behaviour during a partition. What is the trade-off when there is no partition, and why does it exist?
  • 💬 Is PACELC a theorem? What is the formal status of each of its two branches?
  • 💬 Your p99 doubled after a multi-region rollout with no traffic change. Which branch explains it and what would you measure?