00:00

PACELC Theorem in System Design: Latency vs Consistency Explained

Last updated:

The PACELC theorem says that a replicated database faces two trade-offs. If there is a network Partition, it must choose between Availability and Consistency; Else, when the network is healthy, it must choose between Latency and Consistency. It extends the CAP theorem to cover normal operation, where systems spend almost all their time.

This tutorial explains each letter in plain terms, shows where the extra latency comes from, simulates both trade-offs in Java and classifies real databases. It also covers points many guides skip: per-request consistency settings and how quorum sizes set your latency.

1. What Is the PACELC Theorem?

A replicated database keeps copies of the same data, called replicas, on several machines, so it survives failures and serves users from nearby. Keeping copies in step takes time and messages; database replication covers the mechanics. PACELC, proposed by Daniel Abadi in 2010 and published in 2012, names the choices this forces:

  • P (Partition): the network splits, and some replicas cannot reach others.
  • A (Availability): every request still gets an answer.
  • C (Consistency): every read sees the latest successful write.
  • E (Else): the network is working normally.
  • L (Latency): how long a request takes to answer.

A system is described with two halves, such as PA/EL or PC/EC: the first half is its behaviour during a partition, the second its behaviour the rest of the time.

2. Why the CAP Theorem Alone Is Not Enough

The CAP theorem says that during a partition a system cannot be both fully consistent and fully available. That is true, but partitions are rare. Most of the day the network works, and CAP says nothing about that time.

Even on a healthy network, a strongly consistent read must check with other replicas, often in another region, before answering. That costs time on every request, not just during failures. PACELC makes this everyday cost visible, so "we are CP" stops being the whole answer in a design review.

3. How the PACELC Theorem Works

3.1 The Partition Case: A or C

Imagine a food-delivery app with its primary database in Mumbai and a read replica in Singapore. The link between them fails, and a Singapore user asks for the price of paneer tikka. A PA system answers from the Singapore replica, which may hold an old price. A PC system refuses until it can confirm the latest value with Mumbai. Neither is wrong: a menu can show a slightly old price, but a payment must never be charged twice.

3.2 The Else Case: L or C

Now the link is healthy, but Mumbai is about 140 ms away and the local replica 3 ms away. An EL system answers from the local replica, accepting that the latest change may not have arrived. An EC system asks Mumbai, or a majority of replicas, and pays that round trip on every read. Read more about latency and consistency on their own pages.

The two questions of the PACELC theorem A request reaches a replicated database. If the network is partitioned, the system chooses between availability, answering with possibly stale data, and consistency, refusing until the link heals. Otherwise, in normal operation, it chooses between low latency, reading the nearest copy, and consistency, waiting for the primary or a quorum. Request reaches a replicated database Is the network partitioned? yes: P no: E (else) Choose A or C PA: keep answering, possibly with stale data PC: refuse requests until the link heals Choose L or C EL: read the nearest copy, fast but maybe stale EC: wait for the primary or a quorum: slower, fresh
Figure 1: PACELC asks two questions: during a partition, availability or consistency; otherwise, latency or consistency.

4. Where the Latency Comes From

Consistency costs latency because a replica cannot know it holds the newest value without asking others. Many databases use quorums: with N copies, a write waits for W acknowledgements and a read for R responses. If R + W > N, every read overlaps at least one copy that saw the latest write.

The catch is that a request is only as fast as the slowest replica it waits for. R = 2 means waiting for the second-fastest response, so raising W or R buys consistency with latency, and a far-away replica sets your tail latency. Example 3 shows the numbers.

5. PACELC in Java: Code Examples

The examples simulate a primary in Mumbai and a replica in Singapore with fixed delays, so the output is the same on every run. They need Java 17 or later because they use records.

5.1 The Replicated Store

ReplicatedPriceStore.java
package com.javabytechie.pacelc;

import java.util.HashMap;
import java.util.Map;

// A tiny simulation of a primary database and one read replica in the user's region
public class ReplicatedPriceStore {

    public enum ReadPreference { NEAREST_REPLICA, PRIMARY }          // the "Else" choice: latency or consistency
    public enum PartitionPolicy { STAY_AVAILABLE, STAY_CONSISTENT }  // the "Partition" choice

    public record ReadResult(int price, int latencyMs, String servedBy) { }

    static final int REPLICA_LATENCY_MS = 3;    // replica in the user's own region
    static final int PRIMARY_LATENCY_MS = 140;  // primary on another continent

    private final Map<String, Integer> primary = new HashMap<>();
    private final Map<String, Integer> replica = new HashMap<>();
    private final PartitionPolicy partitionPolicy;
    private boolean partitioned;

    public ReplicatedPriceStore(PartitionPolicy partitionPolicy) {
        this.partitionPolicy = partitionPolicy;
    }

    public void write(String item, int price) {
        primary.put(item, price);              // the replica is updated later, by replicate()
    }

    public void replicate() {
        if (!partitioned) {
            replica.putAll(primary);           // asynchronous copy, simulated as one step
        }
    }

    public void setPartitioned(boolean partitioned) {
        this.partitioned = partitioned;        // true: the user's region cannot reach the primary
    }

    public ReadResult read(String item, ReadPreference preference) {
        if (partitioned) {
            if (partitionPolicy == PartitionPolicy.STAY_CONSISTENT) {
                throw new IllegalStateException("primary unreachable, refusing to return possibly stale data");
            }
            return new ReadResult(replica.get(item), REPLICA_LATENCY_MS, "replica");
        }
        if (preference == ReadPreference.PRIMARY) {
            return new ReadResult(primary.get(item), PRIMARY_LATENCY_MS, "primary");
        }
        return new ReadResult(replica.get(item), REPLICA_LATENCY_MS, "replica");
    }
}

5.2 Example 1: The Else Case (EL vs EC)

ElseCaseDemo.java
package com.javabytechie.pacelc;

import com.javabytechie.pacelc.ReplicatedPriceStore.PartitionPolicy;
import com.javabytechie.pacelc.ReplicatedPriceStore.ReadPreference;

public class ElseCaseDemo {

    public static void main(String[] args) {
        ReplicatedPriceStore store = new ReplicatedPriceStore(PartitionPolicy.STAY_AVAILABLE);
        store.write("paneer-tikka", 249);
        store.replicate();

        store.write("paneer-tikka", 279);  // price changes; the replica has not caught up yet

        System.out.println("EL read: " + store.read("paneer-tikka", ReadPreference.NEAREST_REPLICA));
        System.out.println("EC read: " + store.read("paneer-tikka", ReadPreference.PRIMARY));

        store.replicate();
        System.out.println("EL read after replication: " + store.read("paneer-tikka", ReadPreference.NEAREST_REPLICA));
    }
}
Output
EL read: ReadResult[price=249, latencyMs=3, servedBy=replica]
EC read: ReadResult[price=279, latencyMs=140, servedBy=primary]
EL read after replication: ReadResult[price=279, latencyMs=3, servedBy=replica]

The EL read is about 47 times faster but returns the old price, 249. The EC read returns the new price, 279, but takes 140 ms. Once replication catches up, the fast read is also correct.

5.3 Example 2: The Partition Case (PA vs PC)

PartitionDemo.java
package com.javabytechie.pacelc;

import com.javabytechie.pacelc.ReplicatedPriceStore.PartitionPolicy;
import com.javabytechie.pacelc.ReplicatedPriceStore.ReadPreference;

public class PartitionDemo {

    public static void main(String[] args) {
        for (PartitionPolicy policy : PartitionPolicy.values()) {
            ReplicatedPriceStore store = new ReplicatedPriceStore(policy);
            store.write("paneer-tikka", 249);
            store.replicate();

            store.write("paneer-tikka", 279);
            store.setPartitioned(true);   // the link between the regions fails
            store.replicate();            // nothing can be copied now

            try {
                System.out.println(policy + ": " + store.read("paneer-tikka", ReadPreference.PRIMARY));
            } catch (IllegalStateException e) {
                System.out.println(policy + ": error, " + e.getMessage());
            }
        }
    }
}
Output
STAY_AVAILABLE: ReadResult[price=249, latencyMs=3, servedBy=replica]
STAY_CONSISTENT: error, primary unreachable, refusing to return possibly stale data

The available store still answers, with the stale price. The consistent store refuses, and the app must turn that into a friendly message or a retry.

5.4 Example 3: Quorum Sizes and Latency

QuorumLatency.java
package com.javabytechie.pacelc;

import java.util.Arrays;

public class QuorumLatency {

    public static void main(String[] args) {
        int[] replicaLatencyMs = {4, 35, 140};   // three copies: same zone, nearby region, far region
        Arrays.sort(replicaLatencyMs);
        int n = replicaLatencyMs.length;

        int[][] settings = {{1, 1}, {2, 2}, {3, 1}};   // {W, R}
        System.out.println("W  R  overlap?  write ms  read ms");
        for (int[] s : settings) {
            int w = s[0], r = s[1];
            boolean overlap = r + w > n;                 // every read quorum shares a node with every write quorum
            int writeMs = replicaLatencyMs[w - 1];       // wait for the W fastest acknowledgements
            int readMs = replicaLatencyMs[r - 1];        // wait for the R fastest responses
            System.out.printf("%d  %d  %-8s  %8d  %7d%n", w, r, overlap ? "yes" : "no", writeMs, readMs);
        }
    }
}
Output
W  R  overlap?  write ms  read ms
1  1  no               4        4
2  2  yes             35       35
3  1  yes            140        4

W=1, R=1 is fast, but reads may miss the latest write. W=2, R=2 overlaps and costs 35 ms each way. W=3, R=1 gives fast consistent reads, but every write waits 140 ms for the farthest copy and fails if any replica is down.

6. CAP vs PACELC and Real Databases

CAP theoremPACELC theorem
QuestionWhat happens during a partition?What happens during a partition, and what happens otherwise?
When it appliesOnly during network failuresAll the time
ChoicesConsistency or availabilityA or C during a partition; L or C otherwise
Typical labelsCP, APPA/EL, PC/EC, PA/EC, PC/EL
SystemDefault labelNotes
Apache CassandraPA/ELConsistency level per query: ONE is fast, QUORUM or ALL is stronger
Amazon DynamoDBPA/ELReads are eventually consistent by default; ConsistentRead=true gives strongly consistent reads at twice the read cost
Azure Cosmos DBConfigurableFive consistency levels, from Strong to Eventual; the default is Session
Google SpannerPC/ECStrongly consistent reads and writes; commits wait out clock uncertainty
Apache HBasePC/ECEach row is served by one server; its rows are unavailable until another server takes over
Yahoo PNUTSPC/ELThe unusual mix: local, possibly stale reads, but no updates to records whose master is unreachable

These are default behaviours. Per-request settings move many systems between EL and EC, so a single label rarely tells the whole story.

7. Common Mistakes

  • Treating labels as fixed: Cassandra's QUORUM or DynamoDB's ConsistentRead change the Else choice per request.
  • Ignoring the E case: "we are CP" says nothing about the latency users pay every day.
  • Thinking PA/EL loses data: replicas normally converge, but conflicting writes need a rule such as last-write-wins, which can silently drop one update.
  • Claiming "CA": partitions happen in any networked system; CA only describes a single-node database.
  • Testing from one region: a 3 ms local test hides the 140 ms EC cost that distant users pay.
  • Assuming EC needs every replica: a majority quorum is enough; waiting for all copies also hurts availability, as Example 3 shows.

8. Best Practices

  • Decide per operation, not per system: use EC for stock and payments, EL for product pages and feeds.
  • Use read-your-writes where full EC is too slow: users then always see their own changes.
  • Keep quorum members close: place a majority of replicas in nearby zones so EC reads stay fast.
  • Handle PC refusals in the app: use timeouts, retries with backoff and clear error messages.
  • Be honest about staleness: tell users when data may take a few seconds to update.
  • Measure tail latency from where users are: watch p99, not just the average.

9. Summary

  • During a partition, choose availability (PA) or consistency (PC).
  • The rest of the time, choose latency (EL) or consistency (EC).
  • Consistency costs latency because replicas must talk before answering; quorum sizes set how much.
  • Real systems have default labels but often allow per-request choices.
  • Pick the trade-off per operation, based on what stale data would cost.

The PACELC theorem turns "CP or AP?" into the more useful question of what each operation should pay in latency for fresh data. Open the FAQ tab for PACELC theorem interview questions.