CQRS (Command Query Responsibility Segregation)
Separate your read and write models: when CQRS helps, implementation strategies, eventual consistency between models, and common pitfalls.
What Is CQRS?
CQRS (Command Query Responsibility Segregation) is a pattern that separates the operations that read data (queries) from the operations that modify data (commands). Coined by Greg Young and inspired by Bertrand Meyer's Command-Query Separation principle, CQRS takes the idea further by using entirely separate models — and often separate data stores — for reads and writes.
In a traditional CRUD architecture, you have a single model that handles both reads and writes. This works fine for simple systems, but as complexity grows you start to see a painful tension: writes need strong consistency and transactional integrity, while reads need denormalized, fast-access projections optimized for specific UI views. CQRS resolves this tension by letting each side evolve independently.
Commands vs Queries
| Aspect | Command | Query |
|---|---|---|
| Intent | Change state | Return data |
| Return value | Typically void or an ID | Returns data (never changes state) |
| Examples | `PlaceOrder`, `CancelShipment` | `GetOrderById`, `ListUserOrders` |
| Consistency | Strong — uses transactions | Eventual — reads from projection |
| Scalability | Write-optimized DB | Read replicas, caches, search indexes |
Implementation Levels
CQRS exists on a spectrum. You don't have to go all-in on day one:
- Simple CQRS — Single database, but separate command and query objects in code. Low overhead, good separation of concerns.
- Read replica CQRS — Same primary DB for writes, but queries go to a read replica. Easy to implement, the replica lag is the main trade-off.
- Full CQRS — Separate data stores for reads and writes. Write side uses a normalized relational DB; read side uses a denormalized store (e.g., MongoDB, Elasticsearch, Redis). Maximizes performance but adds operational complexity.
Don't Over-Apply CQRS
CQRS adds significant complexity: separate models, eventual consistency, synchronization logic. Only apply it where the read/write performance needs or team scalability genuinely demands it. Simple CRUD applications get no benefit from full CQRS.
Eventual Consistency Between Read and Write
When you use separate read and write stores, the read model is eventually consistent. After a write command completes, it publishes an event (or the system uses CDC) to update the read store. Until that update propagates, queries may return stale data. This is the most common source of bugs in CQRS systems.
Real-World Usage
Amazon uses CQRS extensively in its order management systems. The write side processes orders with ACID guarantees, while read projections power the 'Your Orders' page — a denormalized view optimized for display. Stack Overflow uses a similar pattern where vote counts are written transactionally but cached/projected for fast tag-based queries. Microsoft documents CQRS as a core pattern for Azure microservices.
Common Pitfalls
- Reading your own write — After a command, the UI queries immediately and gets stale data. Mitigate with optimistic UI updates or a short polling retry.
- Projector lag — Under high load, the event bus or projector may fall behind. Monitor consumer lag like a production metric.
- Schema coupling — If the read model schema is tightly coupled to events, changing event schemas becomes painful. Use event versioning.
- Distributed transaction temptation — Developers sometimes try to make commands span multiple services atomically. This defeats the purpose — use Saga instead.
Interview Tip
In interviews, CQRS is often brought up when you need to support very different read and write patterns — for example, a social media feed (complex aggregated reads, simple write path). Frame your answer around *why* you need it: read/write performance asymmetry, independent scaling, or supporting multiple read models. Always mention the eventual consistency trade-off proactively — it shows maturity.