Back to Notes

CQRS Explained: Why Separating Reads from Writes Scales

CQRS Explained: Why Separating Reads from Writes Scales

Most applications start with one database and one model. The same tables, the same ORM entities, and the same service layer handle every request, whether it's a user placing an order or a thousand users browsing the catalog.

This works well for a long time. But as traffic grows, the cracks start to show, especially in apps like e-commerce where reads outnumber writes by 100 to 1 or more.

One model is being asked to serve two very different jobs. That is the problem CQRS is designed to solve.


What is CQRS?

CQRS stands for Command Query Responsibility Segregation. The idea is simple: split your system into two sides, one that changes data and one that reads it.

In a traditional setup, both go through the same model. In CQRS, they get separate modules, and often separate databases.

1┌──────────────┐ ┌──────────────┐
2 Command ──▶│ Write Model │───────▶│ Write DB │
3 │ (validation, │ │ (PostgreSQL) │
4 │ business │ └──────┬───────┘
5 │ rules) │ │ publishes events
6 └──────────────┘ ▼
7 ┌──────────────┐
8 │ Event Broker │
9 │ (Kafka/SQS) │
10 └──────┬───────┘
11 │ consumes events
12 ┌──────────────┐ ┌──────▼───────┐
13 Query ───▶│ Read Model │◀───────│ Read DB │
14 │ (simple, │ │ (MongoDB) │
15 │ no rules) │ └──────────────┘
16 └──────────────┘

Why separate databases?

Because reads and writes want different things:

The write side can stay strict with transactions, constraints, and validation. The read side can store data exactly the way the UI needs it, with no joins at query time, and scale independently across regions.


How the Two Sides Stay in Sync

The moment you have two databases, a new question appears: how does the read DB learn about changes made in the write DB?

The most common answer is events. If you've read my post on Event-Driven Architecture, this will feel familiar.

  1. A command arrives and the write model validates it.
  2. The write model saves the change to the write DB.
  3. It then publishes an event (e.g. OrderPlaced) to a broker like Kafka, SQS, or SNS.
  4. A projector on the read side consumes the event and updates the read DB.

Take an e-commerce example. A customer clicks "Place Order":

The two sides never talk to each other directly. They only share events, which is what keeps them independent.

The catch: eventual consistency

Events take time to travel. Between the write and the projector finishing, the read DB is stale. This is called eventual consistency: the two stores will agree, just not instantly.

Picture this on an e-commerce site: a user places an order, gets redirected to "My Orders", and the new order isn't there yet. To them it looks like the order was lost.

Common ways to deal with it:


When to Use CQRS (and When Not To)

CQRS is powerful, but it is not a default choice. It adds real complexity, so it has to earn its place.

You probably don't need CQRS if:

At this size, the operational cost of two databases, a broker, and projectors is larger than the problem it solves.

CQRS starts to make sense when:

The trade-offs


Closing Thoughts

CQRS is a simple idea (separate reads from writes) with big consequences. It lets each side use the database and data shape it's best at, and it scales well for global, read-heavy systems like e-commerce. The price is eventual consistency and a more complex system.

A good rule of thumb:

Start with one database. Add caching and read replicas first. Reach for CQRS only when the read and write workloads have clearly outgrown a shared model.

If you're under a million users, you likely don't need it yet. But understanding the pattern now will make the decision much easier when your system grows into it.

Back to Notes