
How CQRS Works — Separating Reads from Writes
Most applications use the same model for reading and writing data. A User table stores users. The application reads from it and writes to it. The same schema, the same indexes, the same database. This is straightforward and works well for many systems.
But reads and writes have different needs. Writes need validation, consistency, and normalized data to avoid anomalies. Reads need speed, often joining multiple tables or aggregating data in ways the write schema was not optimized for. When a system grows, optimizing for one hurts the other.
CQRS (Command Query Responsibility Segregation) separates the read side from the write side. Each gets its own model, optimized for its purpose.
The Principle
The idea comes from Bertrand Meyer's CQS (Command Query Separation): a method should either change state (command) or return data (query), never both. CQRS applies this principle at the architectural level.
Commands change state. "PlaceOrder", "UpdateProfile", "CancelSubscription". Directed at the write model. Validated, processed, persisted.
Queries read state. "GetOrderDetails", "ListUserOrders", "SearchProducts". Directed at the read model. Fast, possibly denormalized, possibly cached.
In a CQRS system, these two paths are physically separated.
The Write Model
The write model is the source of truth. It enforces business rules, validates commands, and persists the canonical state. It is typically normalized — no duplicate data, proper foreign keys, ACID guarantees.
When a command arrives:
- Validate the command (does the user exist? is the inventory available?).
- Apply business logic (calculate totals, check limits).
- Persist the change to the write database.
- Emit an event describing what happened ("OrderPlaced", "ProfileUpdated").
The write model does not need to be fast at reading. It doesn't need complex join optimizations or denormalized views. It needs to be correct.
The Read Model
The read model is optimized for queries. It is built from the events emitted by the write model. When an OrderPlaced event arrives, the read model updates a denormalized order_summary table that contains everything the UI needs in a single row — no joins.
The read model can use a completely different storage technology:
- The write model uses PostgreSQL (relational, ACID).
- The read model uses Elasticsearch (full-text search).
- Another read model uses Redis (key-value, sub-millisecond lookups).
- Another read model uses a materialized view in ClickHouse (analytics).
Multiple read models can coexist, each optimized for a different query pattern. This is impossible when reads and writes share a single model.
Event Sourcing Connection
CQRS is often paired with event sourcing. Instead of storing the current state in the write database, event sourcing stores every event that ever happened. The current state is derived by replaying events.
An order's state is not a row in a database — it is the sequence:
OrderCreated { items: [...], total: 99.00 }PaymentReceived { amount: 99.00 }OrderShipped { tracking: "1Z999..." }
The read model subscribes to this event stream and builds materialized views. Need to change how the order summary looks? Rebuild the read model by replaying all events from the beginning.
Event sourcing provides a complete audit trail. You can answer "what was the state of this order at 3:47 PM on Tuesday?" by replaying events up to that timestamp. This is impossible with a mutable database where updates overwrite previous values.
The combination of CQRS + event sourcing is powerful but complex. The write side appends events. The read side projects events into queryable views. The two sides are eventually consistent — after an event is emitted, there is a delay (usually milliseconds to seconds) before the read model reflects it.
Eventual Consistency
In a CQRS system, the read model lags behind the write model. A user places an order (command, write side), then immediately views their orders (query, read side). If the event hasn't been processed yet, the order doesn't appear.
This is eventual consistency — the read model will eventually catch up, but there is a window where it is stale.
Strategies to handle this:
- Read-your-writes — after a write, read from the write model (or wait for the event to be processed) before switching to the read model.
- Optimistic UI — the client assumes the write succeeded and shows the expected result immediately, updating when the read model catches up.
- Version tracking — the client knows the version of its last write and refuses to display read model results from an older version.
When CQRS Is Worth It
CQRS adds complexity: two models to maintain, an event pipeline between them, eventual consistency to handle. It is not a default architecture.
Good fits:
- High read-to-write ratio — 100:1 or higher. Read optimization has massive impact.
- Different scaling needs — reads scale horizontally with replicas, writes need strong consistency.
- Multiple query patterns — search, analytics, and API all need different views of the same data.
- Audit requirements — event sourcing provides a complete history.
- Complex domains — the write model focuses on business rules, the read model focuses on presentation.
Poor fits:
- Simple CRUD applications — the overhead is not justified. A single database with proper indexes handles both reads and writes efficiently.
- Small teams — maintaining two models, event pipelines, and projection logic requires operational maturity.
- Strong consistency requirements — when every read must reflect the latest write, eventual consistency is a problem, not a feature.
CQRS Without Event Sourcing
You don't need event sourcing to use CQRS. A simpler version:
- The write side uses a normalized relational database.
- On each write, the application updates both the write database and the read database (or publishes an event for the read side to process).
- The read database is a denormalized copy optimized for queries.
This gives you the read/write separation without the complexity of an event store. Many teams start here and add event sourcing later if the audit trail and replay capabilities justify it.
Next Steps
- How Consistency Works — understanding the consistency tradeoffs in CQRS.
- How Event-Driven Architecture Works — the event pipeline that connects the write and read models.
- How Transactions Work — the ACID guarantees on the write side.