I first heard about CQRS at a conference in 2013. More than ten years on, most teams still apply it wrong or reach for it when they don’t need to.
The idea is simple: read operations and write operations use different models. The complexity under that simple idea is a lifesaver in some projects and a waste of time in others.
What does CQRS actually do?
In a classic setup you have an Order entity, and the same model is used both for writes and reads. With CQRS:
- Command side: OrderCommand, validation, business rules. Output: a state-change event.
- Query side: OrderView, optimised purely for reads. Usually a separate table or a separate database.
An event bus or direct sync links the two. When a command writes, the query store is updated.
Three places it actually helps
1. Read and write load are wildly different. An e-commerce product catalogue: 10K writes a day (new products, price updates), but 10M reads. A read-optimised denormalised table speeds queries up 10 to 100x. The write side stays normalised.
On a recent WooCommerce build we moved the product-catalogue read side to Redis + Elasticsearch. The write side stayed on MySQL. Product-search latency dropped from 1.5s to 60ms.
2. Reporting and analytics. Reporting off the operational database is usually a disaster. JOINs are slow and production traffic takes the hit. CQRS lets you stand up a separate OLAP store for analytics (BigQuery, ClickHouse, Snowflake) and feed it through events. Reports run without touching operational performance.
3. Complex business rules + simple read requirements. In a financial system the write side might have 50 validation rules (regulatory compliance, fraud detection). The read side just shows the user “my last 10 transactions”. Forcing those two into one model adds complexity for no reason.
Five places it’s overengineering
1. Simple CRUD apps. A blog, an admin panel, a form submission tool. Build it all with CQRS and you write twice the code for zero speedup.
2. Similar read and write load. If the write-to-read ratio is 1:3 or lower, CQRS doesn’t earn its keep. You won’t recoup the cost of the extra event infrastructure.
3. Single-team, small projects. CQRS means running two models, an event pipeline, and dealing with eventual consistency. A three-person team can’t carry that operationally.
4. Strong consistency requirements. If a read right after a write is required (“user placed an order, it has to be in the list now”), CQRS’s eventual consistency is a UX problem. Asking users to “wait a second or two” is rough.
5. Prototype or MVP stage. Standing up two-model plumbing before you know if the product will survive is a waste. Going from simple CRUD to CQRS later is doable, the reverse isn’t.
Getting the benefits without CQRS
Ways to get similar gains without paying the full CQRS price:
Materialised views. PostgreSQL’s MATERIALIZED VIEW, or a denormalised MySQL table. The write side stays the same, but reads hit a view tailored for them. A nightly batch refresh might be enough.
Read replicas. A read-only copy of the database. Heavy queries go to the replica, writes go to the master. One model, split load.
Cache layer. Cache read-heavy data in Redis or Memcached. Invalidate on write. One model, hot data in memory.
These three give you 70 to 80% of the performance benefit CQRS offers at maybe 20% of the operational complexity.
If you do adopt CQRS, watch these
- Event schema versioning. Events will live for five years. Schema changes have to be backward compatible.
- Eventual consistency window. How many seconds after a write is the read side up to date? How does that affect UX?
- Replay strategy. If you need to replay events from scratch, is the infrastructure ready? How long does a rematerialise take?
- Monitoring. Are you tracking sync lag between write and read? Is there an alert when it crosses 30 seconds?
- Failure recovery. What happens when the event bus is down? The write side keeps going, but reads are stale. How do you communicate that to users?
If you don’t have answers to those five, CQRS will bring more chaos than order.
A pragmatic path
My recommendation: adopt CQRS partially. Not across the whole system, just on the two or three critical paths where performance is actually bottlenecked.
Example: in an e-commerce system, order creation is a critical write path (needs tight consistency), product-catalogue search is a critical read path (high volume). Put CQRS on those two. Keep the rest as simple CRUD.
That way you spend complexity where it adds value instead of spreading it everywhere.
Takeaway
CQRS is powerful, but it isn’t a silver bullet. It earns its keep when the read/write load is clearly asymmetric, the consistency requirements differ, and the ops team can run it.
For simpler projects, lighter tools like materialised views, read replicas, and caching give you 80% of the gain. When deciding, ask which real problem it solves. If the answer is “honestly none, it just sounded cool”, skip it.