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.

Engineering6 min read
CQRSSoftware architectureRead modelsEvent sourcingDistributed systems
Share

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.total disagrees with orders.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 surfaceStaleness toleranceRead path
Admin dashboard1–5 secondsProjection table
Customer order history< 1 secondProjection with lag monitor
Payment receiptZeroWrite model or sync read
Search index10–60 secondsAsync projection + rebuild
Agent tool queryDepends on autonomyFreshness 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_at timestamp 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.

Related articles

Command Palette

Search for a command to run...