Skip to content
ExponaBLOG
ENGINEERING CRAFT · DAY 012 / 100

CAP and PACELC without the jargon: a step-by-step guide

What distributed databases really trade off.

Expona Admin6 min read

CAP and PACELC without the jargon

If you've built anything that runs across multiple servers, you've felt the weight of distributed system tradeoffs. Two frameworks—CAP and PACELC—explain what's actually happening when your database chooses between speed, consistency, and resilience. They're not theoretical puzzles; they're the constraints shaping every architecture decision you make.

The real problem: networks fail

Before we talk about CAP, let's be honest about why it matters. In a single-server system, consistency is free. One source of truth. But the moment you replicate data across two or more machines, you've introduced latency and failure modes that simply don't exist on one box.

A network partition isn't a rare edge case you read about in papers. It's something you will experience. Routers fail. Cloud provider issues happen. Cables get cut. When a partition occurs, your replicas can't talk to each other. This is the moment of truth for any distributed system.

The question isn't whether partitions happen. The question is: what does your system do when they do?

CAP in plain words—during a network partition, choose consistency or availability

CAP says you can guarantee at most two of three properties:

  • Consistency: Every read returns the most recent write. All nodes agree on the current state.
  • Availability: Every request gets a response, even if some nodes are unreachable.
  • Partition tolerance: The system continues operating when the network splits.

Here's the key insight: partition tolerance isn't optional. If your system runs on a network, partitions will happen. So CAP really collapses to: when a partition occurs, do you prioritize consistency (CP) or availability (AP)?

Let me show you what this looks like in practice.

CP Example: Payment processing. You have two database replicas in different data centers. A network partition splits them. An incoming payment request arrives at replica A. Should it process?

In a CP system, replica A says no. It can't confirm replica B has been updated, so it refuses the write. The request fails or times out. No duplicate charges. No inconsistent ledgers. The cost is downtime during the partition.

AP Example: Social media likes. You have two replicas. During a partition, replica A increments a like counter. Replica B doesn't see it yet, so it serves a slightly stale count. When the partition heals, the counts merge. Users might see their like counted multiple times briefly, or the count might jump unexpectedly.

The benefit? Your service keeps running. Users don't get 500 errors. You accept eventual consistency as the tradeoff.

Most real systems don't pick pure CP or AP. They're hybrid. But understanding the partition case forces you to ask: which operations can tolerate stale reads, and which cannot?

PACELC adds the normal case—even without partitions, you trade latency for consistency

CAP only talks about what happens during a partition. But networks usually work fine. So PACELC adds a second dimension: else (E), what do you trade in the normal case?

PACELC says: "if there's a Partition, choose between Availability and Consistency (that's CAP). Else (E), choose between Latency (L) and Consistency (C)."

This is where real-world database design gets interesting.

Low-latency, eventually consistent: Write to a local replica immediately, return success to the client, then replicate asynchronously to other nodes. Users see instant confirmation. Other regions see the data a few milliseconds later.

typescript

// Write locally, replicate async
async function createOrder(orderId: string, items: Item[]) {
  // Write to local database immediately
  await localDB.insert('orders', { id: orderId, items, timestamp: Date.now() });
  
  // Return to client without waiting for replication
  res.json({ status: 'created', orderId });
  
  // Replicate asynchronously
  replicationService.queueReplication('orders', orderId);
}

Latency is low. But if a partition happens right after the local write, other regions won't have seen it yet.

High-latency, strongly consistent: Write only after all replicas acknowledge. Users wait longer, but you guarantee all nodes have the same data.

typescript

// Write to all replicas synchronously
async function transferFunds(from: string, to: string, amount: number) {
  const result = await Promise.all([
    replicaA.debit(from, amount),
    replicaB.debit(from, amount),
    replicaC.debit(from, amount),
  ]);
  
  if (result.every(r => r.success)) {
    res.json({ status: 'transferred' });
  }
}

Higher latency. But you guarantee consistency across all nodes before responding.

Most teams encounter PACELC long before they hit a partition. Your normal-case read/write latency often matters more than your partition behavior, because partitions are rare and brief.

Choose per feature, not per system

This is where theory meets reality. You don't build one database for your entire system. You pick the right tradeoff for each feature.

Strong consistency features (CP or low-latency CP):

  • Payment and transaction processing
  • Inventory reservations
  • User authentication and authorization
  • Account balance ledgers
  • Unique constraints (usernames, email addresses)

For these, you accept slower writes or downtime during partitions. The cost of inconsistency is unacceptable.

Eventual consistency features (AP or eventually consistent with async replication):

  • Like counts, view counts, engagement metrics
  • User profile updates (avatar, bio)
  • Follower lists
  • Search indexes
  • Analytics and reporting

For these, you accept stale reads. Users don't mind if a like count is slightly wrong for a few seconds. The benefit is speed and resilience.

A concrete example from my experience: At a ride-hailing platform, we stored driver location using an AP (eventually consistent) pattern. Drivers got fast updates. Small inconsistencies were fine. But driver payment and acceptance of rides used CP with strong consistency guarantees. A driver had to confirm they were actually available when accepting a ride, even if it meant slightly higher latency. Wrong state there would create refund chaos.

Feature CAP Choice Rationale Technology Example
Payment transfer CP Inconsistency = money loss or duplication PostgreSQL with synchronous replication
User notifications AP Occasional missed or late notification is acceptable Redis with async sync to secondary
Inventory count CP Overselling causes fulfillment failure PostgreSQL with row-level locking
Like counter AP Count being off by 10 doesn't matter Cassandra with eventual consistency
User sessions CP Conflicting session state creates security issues Redis with sentinel or cluster

Common mistakes

Assuming you can have all three (C, A, P): You can't, during a partition. Acknowledging this upfront saves you from building systems that promise consistency and availability when the network fails. Pick which one you're willing to sacrifice when partition time comes.

Treating CAP as your only design axis: PACELC often matters more in practice. Your normal-case latency tradeoffs affect user experience far more than your partition behavior. A system that's fast and eventually consistent usually outperforms a system that's slow and always consistent—unless those 30ms of extra latency means missed revenue or a bad UX.

Making one choice for your entire system: A monolithic "we're eventual consistency" or "we're strongly consistent" policy breaks down. Your payment system needs different guarantees than your recommendations engine. Build small, composable systems where each enforces the right tradeoff for its domain.

Forgetting to test partition behavior: It's easy to run well in the normal case. Test what actually happens when replicas can't communicate. Does your conflict resolution merge correctly? Do clients know they're reading stale data? Document and test these paths.

Key takeaways

  • CAP says partitions force you to choose between consistency and availability. Partition tolerance isn't optional—networks fail.
  • PACELC extends CAP by acknowledging your normal case also involves tradeoffs: low latency usually costs immediate consistency.
  • Most systems are hybrid. Choose consistency for payment, accounts, and critical state. Choose availability and eventual consistency for counters, caches, and metrics.
  • Design per feature, not per system. Your database or message broker supports both patterns; use them where they fit.
  • Test partition scenarios. Know what "eventual consistency" actually means for your users: stale reads, duplicate events, ordering issues, or something else.
  • Document your choices. Future maintainers (and your future self) need to know why a feature uses CP or AP, so they don't accidentally break the assumption.
ShareXLinkedInReddit

Found this useful? Support the writing.

Buy me a coffee
Written by
Expona Admin
Site administrator

Comments

Loading comments…

Leave a comment

Comments are reviewed before they appear. Be kind and stay on topic.

Related articles