Definition
- Consistency – linearizability; after a write completes, every subsequent read from any node returns that write or a newer one.
- Availability – every request to a non-failing node receives a non-error response, even if stale.
- Partition tolerance – the system continues to operate despite network breaks that split the cluster.
- CP system – during a partition, refuses requests it cannot guarantee are consistent (e.g., ZooKeeper, etcd).
- AP system – during a partition, keeps serving on both sides and resolves conflicts later (e.g., Cassandra, DynamoDB).
- Key insight – partitions are unavoidable, so the real choice is CP vs AP; "CA" is a category error in real distributed systems.
Imagine the National Bank of India has two branches connected by a phone line, and both must agree on Rajesh's balance. One day the phone line breaks (a partition). Now each branch faces a choice: either refuse to serve Rajesh until the line is restored so the two branches never disagree (CP), or keep serving Rajesh at both branches and reconcile later even though the two copies might briefly differ (AP). You cannot both stay open and stay perfectly in sync while the line is down.
Understanding the Concepts
What is the CAP theorem?
The CAP theorem states that a distributed data store can guarantee at most two of three properties simultaneously: Consistency, Availability, and Partition tolerance. In practice, because partitions are unavoidable, the choice reduces to CP vs AP.
CAP Theorem
┌─────────┐
C │ │ A
Consistency Availability
│ │
└────┬────┘
│
P
Partition Tolerance
Real systems must tolerate partitions.
So the choice during a partition is:
CP = Consistent + Partition-tolerant
AP = Available + Partition-tolerant
What does each property mean precisely?
Consistency in CAP means linearizability: after a write completes, every subsequent read from any node returns that write or a newer one. Availability means every request to a non-failing node gets a response. Partition tolerance means the system works despite network breaks.
package com.bank.model;
/**
* Illustrates CAP properties with banking examples.
*/
public class CAPProperties {
// CONSISTENCY: Rajesh deposits 50000 at Branch A.
// Any read from Branch B immediately returns the updated balance.
// If Branch B returns an old balance, consistency is violated.
// AVAILABILITY: Rajesh walks into Branch B and asks for his balance.
// Branch B must respond, even if the answer might be stale.
// If Branch B says "sorry, system down", availability is violated.
// PARTITION TOLERANCE: The network between Branch A and B is down.
// The system must still function (either CP or AP behavior).
}
Why is partition tolerance mandatory?
In any system that spans more than one machine over a network, partitions will happen. Cables fail, switches reboot, data centers lose connectivity. You cannot choose to "not tolerate" partitions because they occur whether you like it or not.
Partition Scenario: Branch A (Delhi) Branch B (Mumbai) ┌──────────────┐ ┌──────────────┐ │ Rajesh │ │ Rajesh │ │ Balance: │ NETWORK │ Balance: │ │ 100000 │ ───── X ──── │ 100000 │ └──────────────┘ (broken) └──────────────┘ Rajesh deposits 50000 at Branch A. Branch A now has 150000, Branch B still has 100000. CP: Branch B refuses to serve until network is restored. AP: Branch B serves 100000 (stale) and reconciles later.
What is a CP system?
A CP system, during a partition, refuses to serve requests it cannot guarantee are consistent. It returns errors or blocks rather than risk returning stale data. Examples include ZooKeeper, etcd, and a single-primary database with synchronous replication.
package com.bank.service;
import java.math.BigDecimal;
import java.sql.*;
import java.util.logging.Logger;
/**
* CP behavior: the account service refuses rather than
* risk returning a stale balance during a partition.
*/
public class CPAccountService {
private static final Logger LOG = Logger.getLogger(
CPAccountService.class.getName());
private final Connection primaryConnection;
public CPAccountService(Connection primaryConnection) {
this.primaryConnection = primaryConnection;
}
public BigDecimal getBalance(String accountId) throws SQLException {
// Reads from primary only: consistent but may be unavailable
// during a partition if the primary is unreachable
String sql = "SELECT balance FROM accounts WHERE account_id = ?";
try (PreparedStatement ps = primaryConnection.prepareStatement(sql)) {
ps.setString(1, accountId);
try (ResultSet rs = ps.executeQuery()) {
if (rs.next()) {
return rs.getBigDecimal("balance");
}
throw new SQLException("Account not found: " + accountId);
}
}
// If primaryConnection fails (partition), this throws an exception
// rather than returning stale data from a replica.
}
}
What is an AP system?
An AP system, during a partition, keeps accepting reads and writes on both sides and resolves conflicts later through techniques like last-write-wins, version vectors, or CRDTs. Examples include Cassandra and DynamoDB.
package com.bank.service;
import java.util.logging.Logger;
/**
* AP behavior: the feed service returns whatever data
* is available locally, even if it is stale.
*/
public class APFeedService {
private static final Logger LOG = Logger.getLogger(
APFeedService.class.getName());
public String getActivityFeed(String customerId) {
// Reads from the nearest replica: always available
// but may be stale during a partition
LOG.info("Serving feed from local replica for " + customerId);
return "Recent activity (may be slightly stale)";
// Conflicts resolved later via last-write-wins or CRDT
}
}
How does CAP shape microservices data design?
Each microservice owns its own database, and services communicate over the network, so cross-service consistency is a distributed-systems problem. Most microservice architectures lean AP for cross-service state and use CP locally for hard invariants.
package com.bank.service;
import java.math.BigDecimal;
import java.util.logging.Logger;
/**
* Pankaj's architecture: CP for money, AP for everything else.
*/
public class TransferOrchestrator {
private static final Logger LOG = Logger.getLogger(
TransferOrchestrator.class.getName());
private final CPAccountService accountService; // CP: strong consistency
private final APFeedService feedService; // AP: eventual consistency
public TransferOrchestrator(CPAccountService accountService,
APFeedService feedService) {
this.accountService = accountService;
this.feedService = feedService;
}
public void transfer(String sourceId, String targetId, BigDecimal amount) {
// Step 1: CP - debit and credit must be strongly consistent
// Uses ACID transaction on the primary database
LOG.info("Executing transfer with strong consistency");
// Step 2: AP - notify feed asynchronously via Kafka event
// The feed can lag a few seconds; that is acceptable
LOG.info("Publishing transfer event for feed update (eventual consistency)");
}
}
What are common mistakes when applying CAP?
The most common mistake is applying one blanket label (CP or AP) to the entire system. CAP should be applied per operation and per data boundary.
Wrong: "Our system is AP." Right: Per-operation CAP classification: Operation CAP Choice Reason ───────────────────── ────────── ────────────────────── Balance check CP Money must never be wrong Fund transfer CP Double-spend is unacceptable Activity feed AP Staleness is harmless Profile picture AP Lag is cosmetic Notification AP Delay is acceptable
What should a senior engineer know?
CAP only describes behavior during a partition. When the network is healthy, a well-designed system can be both consistent and available. The trade-off only forces a choice while partitioned. "CA" is a category error because the moment a partition happens, you are forced back to CP or AP.
-- Practical example: saga pattern for cross-service transfer
-- Account Service (CP): debit locally with ACID
UPDATE accounts SET balance = balance - 10000.00
WHERE account_id = 'SAV2001' AND balance >= 10000.00;
-- If the credit service is unreachable (partition):
-- CP choice: abort and compensate (reverse the debit)
-- AP choice: queue the credit and process it when the partition heals
-- The saga pattern uses compensating actions:
-- 1. Debit source (committed)
-- 2. Try credit target
-- 3. If credit fails, compensate: reverse the debit
Visual Overview
┌──────────────────────────────────────────────────────┐ │ CAP Theorem │ ├──────────────────────────────────────────────────────┤ │ │ │ C (Consistency) │ │ Every read sees the latest write │ │ / \ │ │ / \ │ │ CP ──/──────────────\── CA (not real) │ │ / \ │ │ P ──────────────── A │ │ (Partition tolerance) (Availability) │ │ \ / │ │ \── AP ────────/ │ │ │ │ Partitions are mandatory ──► P is always chosen │ │ Real choice: CP or AP │ │ │ ├──────────────────────────────────────────────────────┤ │ Microservices: per-data-boundary classification │ │ │ │ ┌─────────────┐ ┌──────────────┐ │ │ │ Account Svc │ │ Feed Svc │ │ │ │ (CP) │ │ (AP) │ │ │ │ PostgreSQL │ │ Cassandra │ │ │ │ Single │ │ Multi-node │ │ │ │ primary │ │ Eventual │ │ │ └──────┬──────┘ └──────┬───────┘ │ │ │ Kafka event │ │ │ └──────────────────┘ │ └──────────────────────────────────────────────────────┘
Key Takeaway
The CAP theorem says a distributed data store can guarantee at most two of Consistency, Availability, and Partition tolerance at the same time. The key insight is that partitions are not optional in any real multi-node system, so partition tolerance is mandatory, which means the genuine choice during a partition is between consistency and availability: CP versus AP.
A CP system refuses or blocks requests it cannot guarantee are consistent, like ZooKeeper, etcd, or a single relational primary with synchronous replication. An AP system keeps serving on both sides of the split and reconciles conflicts later, like Cassandra or DynamoDB. CAP only bites during a partition; when the network is healthy a system can be both consistent and available.
For microservices this is central because each service owns its own database and they communicate over the network. Most designs lean AP for cross-service consistency: they accept eventual consistency, propagate changes through Kafka events with the outbox pattern, and coordinate with sagas instead of global locks. But Pankaj applies CAP per operation, not globally. At the National Bank of India, the Account service that guards balances is CP: during a partition it refuses rather than risk a double-withdrawal. The Feed and Notification services are AP because a few seconds of staleness is harmless.
The senior point is that applying one blanket label to the entire system is wrong. An earlier design that applied one blanket AP policy briefly allowed a double-spend during a partition, which was unacceptable for money, and the team fixed it by making balances CP locally.
Remember one sentence: partitions are inevitable so the real choice is CP versus AP, and you decide it per data boundary based on whether the data guards a hard invariant.
---