Skip to content

CAP Theorem

Suppose the product service of our store keeps its data in PostgreSQL replicated on two nodes, one in each data centre, so that the store survives the loss of a whole site. One afternoon the link between the two sites goes down, but both nodes are still alive and still receiving requests. A customer on site B asks for the stock of product 42, which was just updated on site A. Node B has two options: answer with what it has (possibly stale) or refuse to answer until it can talk to A again. There is no third option β€” and this is exactly what the CAP theorem formalises.

The CAP Theorem, proposed by Eric Brewer in 2000 and formally proven by Seth Gilbert and Nancy Lynch in 20021, states that a distributed system that replicates data across nodes cannot simultaneously guarantee all three of the following properties3:

CAP theorem Three overlapping circles: Consistency, Availability and Partition tolerance. The overlaps are CP and AP, the two real choices, and CA, which only exists without partitions. The centre, all three at once, is impossible. Consistency every read sees the latest write Availability every request gets a non-error answer Partition tolerance works while the network is split CP ZooKeeper, etcd AP Cassandra, DNS CA no partitions (single node) all three: impossible Partitions will happen, so the real choice is CP or AP β€” during a partition.


The three properties

Consistency (C) : Every read returns the most recent write, or an error. All nodes see the same data at the same time β€” equivalent to linearisability (the system behaves as if there were a single copy of the data and every operation took effect at one instant). In a consistent system, it is impossible to read stale data.

Availability (A) : Every request received by a non-failing node receives a non-error response β€” even if the response may not contain the most recent write.

Partition Tolerance (P) : The system continues to operate despite arbitrary network partitions β€” message drops or delays between nodes. A partition splits the nodes into groups that can still run but cannot talk to each other. Partitions are a fact of life in any real distributed network; hardware failures, congestion, and physical distances all cause them.

Partition Tolerance is not optional

In any real-world distributed system, network partitions will happen. The practical choice is therefore between C and A during a partition β€” not whether to include P.

The C in CAP is not the C in ACID

In ACID database transactions, consistency means "the transaction leaves the data valid according to the constraints" (foreign keys, CHECK, invariants). In CAP, consistency means linearisability: every replica answers as if there were only one copy. A single PostgreSQL server is ACID-consistent; the question CAP asks only appears when there are replicas.


A partition, step by step

The same scenario played twice: once by a CP system and once by an AP system.

sequenceDiagram
    participant CA as Client A
    participant N1 as Node 1 (majority side)
    participant N2 as Node 2 (isolated)
    participant CB as Client B
    Note over N1,N2: 3 replicas: Node 1 and Node 3 can still talk, Node 2 is cut off
    CA->>N1: write stock[42] = 0
    N1-xN2: replicate (link is down)
    N1-->>CA: OK
    CB->>N2: read stock[42]
    alt CP: choose consistency
        N2-->>CB: error: cannot reach majority
    else AP: choose availability
        N2-->>CB: stock[42] = 5 (stale)
    end
    Note over N1,N2: partition heals, replicas sync

Neither answer is "wrong": the CP node refuses rather than lie; the AP node answers with the best value it has. Which one is acceptable depends on the data, not on the technology.


System classes

CP β€” Consistent and Partition-Tolerant

During a partition, the system refuses requests rather than returning potentially stale data. When the partition heals, consistency is restored before serving again.

Examples: Apache ZooKeeper, etcd, PostgreSQL with synchronous replication, MongoDB (with majority write concern and primary reads, the defaults).

Use when: correctness is non-negotiable β€” financial transactions, inventory systems, identity stores.

flowchart LR
    CA(["Client A"]) -->|"1 Β· write stock = 0"| A[("Node A<br/>stock = 0")]
    A -.-x|"2 Β· replicate βœ— partition"| B[("Node B<br/>stock = 5")]
    CB(["Client B"]) -->|"3 Β· read stock"| B
    B -->|"4 Β· error: unavailable"| CB

AP β€” Available and Partition-Tolerant

During a partition, every node continues to respond, but nodes may diverge and return different data. After the partition heals, the system converges to a consistent state (eventual consistency).

Examples: Apache Cassandra, Amazon DynamoDB, CouchDB, DNS.

Use when: availability matters more than momentary accuracy β€” shopping carts, social media feeds, search indexes.

flowchart LR
    CA(["Client A"]) -->|"1 Β· write stock = 0"| A[("Node A<br/>stock = 0")]
    A -.-x|"2 Β· replicate βœ— partition"| B[("Node B<br/>stock = 5")]
    CB(["Client B"]) -->|"3 Β· read stock"| B
    B -->|"4 Β· stock = 5 (stale)"| CB
    A ==>|"5 Β· partition heals: sync"| B

CA β€” Consistent and Available

Assumes no partitions, which is only realistic for single-machine systems or tightly controlled LANs. Not a practical category for distributed systems.


Comparison table

CP AP CA
Consistency βœ… Always ❌ Eventually βœ… Always
Availability ❌ During partition βœ… Always βœ… Always
Partition tolerance βœ… βœ… ❌ (unrealistic)
Trade-off Reject requests during partition Return possibly stale data Only works on single node
Examples ZooKeeper, etcd, PostgreSQL (sync replication) Cassandra, DynamoDB, DNS Single-node RDBMS

CP and AP are labels for a configuration, not for a product

Most databases let you tune the trade-off per request: Cassandra reading and writing with QUORUM behaves much more like a CP system, and PostgreSQL serving reads from an asynchronous replica can return stale data. Many real systems are, strictly speaking, neither fully CP nor fully AP5. Ask "how is it configured, and what happens to my requests during a partition?" rather than "is this database CP or AP?".


The PACELC extension

The CAP theorem only addresses the partition scenario. PACELC (Daniel Abadi, 2010)2 extends it by asking: even when there is no partition, what is the trade-off?

flowchart LR
    Q{"Is there a<br/>partition?"} -->|"yes Β· P"| PAC{"Availability<br/>or Consistency?"}
    PAC -->|A| PA["PA: answer, maybe stale"]
    PAC -->|C| PC["PC: refuse or wait"]
    Q -->|"no Β· E (else)"| ELC{"Latency<br/>or Consistency?"}
    ELC -->|L| EL["EL: answer before<br/>the replicas confirm"]
    ELC -->|C| EC["EC: wait for<br/>the replicas"]

The reason is replication: to be consistent, a write must wait for other replicas to acknowledge it (a network round-trip, possibly to another region); to be fast, it must answer before they do β€” and accept that a replica may briefly lag behind.

System Partition behaviour Normal behaviour Classification
Cassandra Favours A Favours L (low latency) PA/EL
DynamoDB Favours A Favours L PA/EL
MongoDB (majority writes) Favours C Favours C PC/EC
PostgreSQL (synchronous replication) Favours C Favours C PC/EC

PACELC4 is more useful than CAP for making day-to-day database choices, because most of the time there is no partition β€” yet the latency/consistency trade-off is always present.

PACELC in the store: read replicas

A common way to scale reads is to add an asynchronous read replica to PostgreSQL and send GET /products to it. Reads become faster and the primary is relieved β€” the EL choice. The price is replication lag: right after an admin updates a price on the primary, the replica may still return the old one for a few milliseconds (or seconds under load). The Redis cache of Hands-on 8 makes the same choice: a cached product can be stale until its TTL expires.


Practical implications for system design

E-commerce product catalogue β†’ AP : Showing a product as in-stock when it has just gone out of stock is acceptable. A brief period of stale data does not harm correctness.

Shopping cart β†’ AP : Adding an item to a cart that temporarily diverges across replicas is fine; the cart can reconcile on checkout.

Payment processing β†’ CP : A double charge or missed debit cannot be reconciled after the fact. Consistency is required.

User authentication β†’ CP : A revoked token must be immediately invalid across all nodes. Stale auth state is a security vulnerability.

Most systems mix both

A single application can use an AP store (Cassandra) for user activity logs and a CP store (PostgreSQL) for financial records. Choose per data domain, not per application.

Common mistakes

  • "We chose CA." If the data is on more than one machine, partitions can happen; CA only means "we never thought about what happens during one".
  • Treating CAP as a permanent property. The C/A choice only applies during a partition; the rest of the time the trade-off is latency (PACELC).
  • Forgetting the cache. Adding Redis in front of a CP database makes those reads AP-like: the cache can serve stale data.

Connections

  • Consensus & Algorithms β€” how CP systems such as etcd actually stay consistent: majorities and Raft.
  • Hands-on 8 β€” a Redis cache, an explicit choice of latency over consistency.
  • Event-Driven Architecture β€” asynchronous events make services eventually consistent with each other.

Key takeaways

  • CAP: when the network partitions, a replicated store must choose between consistency (refuse) and availability (answer, maybe stale).
  • P is not optional, so the real choice is C or A during a partition; CA only exists on a single node.
  • CAP's C is linearisability β€” not ACID's C.
  • PACELC: even without a partition, replication forces a choice between latency and consistency.
  • Choose per data domain and per configuration: payments and auth CP, catalogue and cart AP.