CQRS is a read-model problem, not a write-model problem
CQRS demos focus on command handlers and event stores. Production CQRS fails on the read side — projection lag, semantic drift, and queries nobody designed. The read model is the hard part; the write path is the easy part.
Command Query Responsibility Segregation splits writes from reads so each side optimizes independently. Conference talks make this sound like a write-side ceremony: aggregates, command buses, event stores. Production incidents tell a different story. CQRS read model problems — stale projections, drift between command truth and query tables, dashboards showing data that contradicts what the user just saved — cause the support tickets. The write path is often a well-tested CRUD mutation with extra steps. The read path is a denormalized warehouse nobody owns, updated asynchronously by a projection worker that fell behind during deploy.
Teams that adopt CQRS by splitting the write model first inherit eventual consistency without designing for it. They get two databases, an event bus, and lag measured in seconds that becomes minutes under load — with no reconciliation, no freshness contract, and no answer when a user asks "why doesn't my change show up?"
Write-side CQRS is the part teams already know how to do
The write model in CQRS is domain logic the team was already writing — encapsulated in aggregates or services, validated, persisted. Adding commands and events changes the transport, not the fundamental difficulty. Most business rules already lived on the write path: invariants, authorization, validation.
The seductive part is architectural purity. Rename updateOrder to ShipOrderCommand, emit OrderShipped, feel mature. The read model is deferred: "we'll project to a query table later." Later arrives when the first dashboard reads from orders_view that lags three seconds behind orders and customer support files a bug that cannot be reproduced on the write side.
CQRS without a read-model strategy is two databases and a prayer.
Eventual consistency is not a fixed delay. Under normal load, projection lag might be 50 ms. Under deploy, broker rebalance, or projection worker restart, lag stretches to seconds or minutes. The system promises consistency eventually — not when. Consumers that assume freshness — humans refreshing a page, agents reading state to decide the next action — break silently.
The read model defines what CQRS is for
The read model exists because queries have different shape than writes. A write stores normalized order rows. A read needs user_orders_view with customer name, item titles, and status pre-joined — zero joins at query time, sub-millisecond reads at the cost of asynchronous updates.
Designing the read model means answering questions the write model does not ask:
- Which queries does the product run? List views, detail pages, search, aggregations, exports — each may need a different projection.
- What staleness is acceptable per query? Dashboard: 5 seconds may be fine. Payment confirmation: near-zero — read from write model or synchronous projection.
- Who owns projection correctness? When
orders_view.totaldisagrees withorders.total, which is truth and who fixes the pipeline? - How is drift detected? Reconciliation jobs comparing write-side state to projections on a schedule — not discovered by users.
| Query surface | Staleness tolerance | Read path |
|---|---|---|
| Admin dashboard | 1–5 seconds | Projection table |
| Customer order history | < 1 second | Projection with lag monitor |
| Payment receipt | Zero | Write model or sync read |
| Search index | 10–60 seconds | Async projection + rebuild |
| Agent tool query | Depends on autonomy | Freshness contract required |
The event sourcing without ceremony pattern covers lightweight append logs with inline projections. Full CQRS adds multiple read models per write stream — each a product decision, not a framework default.
Projection lag is a correctness bug, not a performance metric
Teams monitor broker offset lag and call it healthy at 10,000 events behind. Users call it broken when their update does not appear. Business-meaningful lag metrics matter: "order visibility delay p99 in seconds," "search index freshness," "dashboard data age."
Failure modes:
Gradual slowdown. Projection worker processes fewer events per second than the write side produces. Lag grows from seconds to hours before anyone notices. The read model is materially wrong; catching up takes longer than stakeholders tolerate.
Poison events. One malformed event crashes the projection handler. Processing stops. Write side continues. Divergence compounds.
Semantic drift. Write model evolves — new fields, renamed statuses. Projection code lags. Read model speaks a different language than the command side. Not timing disagreement — meaning disagreement.
Reconciliation jobs repair drift: compare write-side truth to projection state, emit corrections, alert on mismatch. Build reconciliation before the first incident, not after. The pattern is the same as event sourcing append logs — truth on one side, derived state on the other, explicit repair path.
CQRS and AI agents break on stale reads
Autonomous agents read state, decide, act, read again. Eventual consistency turns the loop into context drift: the agent acts on stale data, observes stale data again, replans incorrectly, burns tokens on contradictory actions. Humans refresh and wait. Agents trust what they read.
Agent-facing read models need explicit freshness contracts:
- Return
projected_attimestamp with every query response. - Reject or flag reads when lag exceeds threshold for autonomous workflows.
- Route high-stakes agent queries to write model or synchronous path.
CQRS for human dashboards tolerates seconds of lag. CQRS for agent orchestration without freshness guarantees is an accident waiting for a production trace.
When does CQRS justify the read-model complexity?
These questions determine whether CQRS earns its operational cost for a bounded context.
Should this bounded context use CQRS at all?
CQRS justifies when read and write load profiles diverge sharply — many reads per write, expensive joins, multiple query shapes over the same data — and when the team can accept eventual consistency for most queries with explicit exceptions. Skip CQRS when read and write patterns are similar, when strong consistency is required everywhere, or when the team lacks capacity to own projections and reconciliation.
How many read models are enough?
One read model per distinct query pattern — not one per screen. order_list_view and order_detail_view may merge if the detail view is a keyed lookup on the same denormalized table. Search indexes, analytics aggregates, and export formats are separate read models with separate staleness contracts.
What happens when projection and write model disagree?
The write model wins. Projections are derived and repairable. Run reconciliation, identify the divergence source, replay events if needed, fix projection code, document the incident. Never "fix" the write model to match a stale projection. The modular monolith can host CQRS inside module boundaries before the network multiplies lag and failure domains.
A common argument runs the other way
The opposing view holds that CQRS is over-engineering — that read replicas, materialized views, and caching solve read scaling without separate command and query models.
Read replicas and materialized views work for many systems. CQRS adds value when multiple heterogeneous read models must evolve independently from one write stream, when event replay must rebuild projections after logic changes, or when write and read scaling require different storage technologies. The mistake is applying CQRS for resume-driven architecture when Postgres read replicas would suffice.
Key takeaways
- CQRS production failures are read-model failures: lag, drift, missing reconciliation.
- Design read models first — query shapes, staleness contracts, ownership.
- Eventual consistency lag is unbounded under load; monitor business-meaningful freshness.
- Reconciliation jobs compare write truth to projections before users find drift.
- Agent consumers need freshness metadata; stale reads cause autonomous errors.
- Write-side ceremony is easy; read-side operations are where CQRS lives or dies.
Conclusion
CQRS training emphasizes commands. CQRS operations emphasize projections. The teams that succeed start with the queries their product actually runs, define how stale each answer can be, and build reconciliation before the projection worker falls behind for the first time.
The architecture review question is not "should we separate reads and writes?" It is "which read models do we need, who owns them, and what happens when they lie?" Answer that before splitting the write path. The write side was never the hard part.