---
title: "05 — Consistency Trade-offs — CQRS, ACID, and the CAP Theorem"
uid: consistency-tradeoffs-cqrs-acid-cap
tags: ["cap-theorem", "cqrs", "eventual-consistency", "distributed-systems", "roadmap:software-architect", "acid", "databases"]
excerpt: "ACID/CAP and CQRS/eventual consistency are the formal names for trade-offs you're already making — the question isn't which is correct, but which guarantee you give up, on purpose."
date: 2026-08-13T03:27:42+0000
source: https://www.aveshina.my.id/en/blog/consistency-tradeoffs-cqrs-acid-cap
---

Exam trivia was how I filed ACID/CAP and CQRS — until the first time I had to choose between a consistent read and an available one. The model that made them click: **they're the formal names for trade-offs you're already making, whether you admit it or not** [1][2]. Once I could name the trade-off, the question stopped being "which is correct" and became "which guarantee am I willing to give up, and on purpose."

These two ideas are the foundation for reasoning about any system that spans more than one process or one database. Almost every "why is our data weird" incident traces back to a trade-off someone made without realizing it was a trade-off.

## ACID: what a transaction guarantees

ACID describes the guarantees a database transaction provides — **atomicity, consistency, isolation, and durability** — ensuring reliable operations even under failure [1]:

- **Atomicity** — all of the transaction happens, or none of it does. No half-written state.
- **Consistency** — the transaction takes the database from one valid state to another, respecting all constraints.
- **Isolation** — concurrent transactions don't interfere with each other as if they ran one at a time (modulo the isolation level).
- **Durability** — once committed, the change survives a crash.

ACID is the default way of thinking for a single relational database — PostgreSQL, MySQL, and friends deliver these guarantees by default. It's also the model I had to unlearn the moment a system grew past one database.

## CAP: the distributed-systems constraint

The CAP theorem states that a distributed system can guarantee **only two of three** properties at once: **consistency, availability, and partition tolerance** [2]. Because network partitions are a fact of life — cables fail, switches fail, nodes go briefly unreachable — partition tolerance isn't really optional in a distributed system. So the practical choice is between consistency and availability _when a partition happens_:

```figure
<svg viewBox="0 0 600 320" xmlns="http://www.w3.org/2000/svg" class="my-6 w-full max-w-2xl" role="img" aria-label="CAP theorem triangle. Three corners labeled Consistency, Availability, Partition tolerance. In a distributed system, Partition tolerance is mandatory because networks partition. So the real choice during a partition is between Consistency (CP — refuse requests that can't be verified consistent) and Availability (AP — keep answering, possibly with stale data).">
  <g font-family="ui-sans-serif, system-ui, sans-serif" text-rendering="geometricPrecision">
    <!-- triangle -->
    <polygon points="300,40 90,260 510,260" fill="none" stroke="#475569" stroke-width="1.5"/>
    <!-- corners -->
    <circle cx="300" cy="40" r="6" fill="#0d9488"/>
    <text x="300" y="28" font-size="13" font-weight="700" fill="#134e4a" text-anchor="middle">Consistency</text>
    <circle cx="90" cy="260" r="6" fill="#6366f1"/>
    <text x="90" y="284" font-size="13" font-weight="700" fill="#1e1b4b" text-anchor="middle">Availability</text>
    <circle cx="510" cy="260" r="6" fill="#7c3aed"/>
    <text x="510" y="284" font-size="13" font-weight="700" fill="#4c1d95" text-anchor="middle">Partition tolerance</text>

    <!-- edges -->
    <text x="180" y="150" font-size="11" fill="#0f766e" text-anchor="middle" transform="rotate(-58 180 150)">CP — pick consistency</text>
    <text x="420" y="150" font-size="11" fill="#3730a3" text-anchor="middle" transform="rotate(58 420 150)">AP — pick availability</text>
    <text x="300" y="280" font-size="10.5" fill="#64748b" text-anchor="middle" font-style="italic">(mandatory: networks partition)</text>
  </g>
</svg>
```

- **CP (consistency + partition tolerance)** — during a partition, the system refuses requests it can't verify are consistent. You'd rather be unavailable than wrong. Think: a banking ledger.
- **AP (availability + partition tolerance)** — during a partition, the system keeps answering, possibly with stale data that reconciles later. You'd rather be eventually consistent than unavailable. Think: a social feed [2].

Architects use CAP to reason about database and system trade-offs deliberately. A system that silently picks AP for data that needed CP is where data-corruption bugs live.

## Eventual consistency

Eventual consistency is the consequence of choosing AP — updates propagate across the system over time instead of instantly [3]. All replicas _eventually_ converge to the same value if no new updates arrive, but at any given moment, two readers might see different values. This is acceptable for a like-count on a social post and unacceptable for a balance on a withdrawal. Eventual consistency isn't a bug; it's a chosen property — and it's the one that lets distributed systems scale.

## CQRS: splitting reads from writes

CQRS — Command Query Responsibility Segregation — separates the operations that **change** data (commands) from the operations that **read** data (queries), often using different models for each [3][4]. The read side might be a denormalized projection optimized for fast lookups, while the write side is a normalized model optimized for validation and consistency.

```
Write path (commands)   →   Write model (optimized for validation)
                                         │
                                         ▼  (events / sync)
Read path  (queries)    →   Read model  (optimized for fast lookups)
```

This separation pairs naturally with eventual consistency — the read model often lags the write model by milliseconds or more, updated asynchronously [3]. The trade-off is real: CQRS adds complexity (two models, a sync mechanism) in exchange for the ability to scale reads and writes independently and to shape each model for its actual workload. It's common in systems where reads vastly outnumber writes, or where the read shape differs wildly from the write shape.

## The single thread

ACID is what you get in a single box. CAP is the wall you hit the moment there's more than one box. Eventual consistency and CQRS are deliberate responses to that wall — choosing availability and scaling reads/writes separately, at the cost of immediate consistency and added complexity. The architect's job is to know which side of the wall each piece of data lives on, and to make the choice deliberately.

## How I use this

For each piece of data, I ask: does the reader need to see every write immediately (CP/ACID), or is "eventually correct" fine (AP/eventual)? Balances and inventory lean CP; feeds, counters, and search indexes lean AP. When read and write shapes diverge sharply, CQRS earns its complexity — otherwise a single well-indexed model is simpler and I don't reach for the split. The discipline is making the trade-off explicit in the design doc rather than discovering it during an incident.

## References

[1] "ACID," Wikipedia. [Online]. Available: [https://en.wikipedia.org/wiki/ACID](https://en.wikipedia.org/wiki/ACID)

[2] "What is CAP Theorem?," BMC Blog. [Online]. Available: [https://www.bmc.com/blogs/cap-theorem/](https://www.bmc.com/blogs/cap-theorem/)

[3] M. Fowler, "CQRS," martinfowler.com. [Online]. Available: [https://martinfowler.com/bliki/CQRS.html](https://martinfowler.com/bliki/CQRS.html)

[4] Microsoft, "CQRS pattern," Azure Architecture Center. [Online]. Available: [https://learn.microsoft.com/en-us/azure/architecture/patterns/cqrs](https://learn.microsoft.com/en-us/azure/architecture/patterns/cqrs)

```quiz
Q: In the CAP theorem, which property is effectively non-optional in a real distributed system?
- Consistency
- Availability
- Partition tolerance
correct: 2
explain: Network partitions are unavoidable, so partition tolerance is mandatory. The real choice during a partition is between consistency and availability.

Q: A system keeps serving reads during a network partition, even though some replicas may have stale data. Which CAP choice is this?
- CP — consistency and partition tolerance
- AP — availability and partition tolerance
correct: 1
explain: Choosing to keep answering during a partition, accepting stale data that reconcils later, is AP. It's eventual consistency in practice.

Q: What does the "A" in ACID guarantee?
- Atomicity — all of a transaction happens, or none of it does
- Availability — the system answers every request
correct: 0
explain: ACID's A is Atomicity: a transaction is all-or-nothing. Availability in the CAP sense is a separate concept about always answering requests.

Q: CQRS is best described as…
- combining reads and writes into one optimized model
- separating command (write) operations from query (read) operations, often with different models
correct: 1
explain: CQRS splits the write side from the read side so each model can be optimized for its workload, at the cost of extra complexity to keep them in sync.

Q: A social-feed like-count uses eventual consistency. Why is that an acceptable trade-off?
- Like-counts need to satisfy ACID across all replicas instantly
- A reader seeing a slightly stale count is fine; forcing CP would hurt availability and scale
correct: 1
explain: Like-counts tolerate AP/eventual consistency because a briefly stale value is harmless. Forcing strong consistency would cost availability and scale for no real benefit.
```
