Trade-offs · Production decisions
CQRS vs one model for reads and writes
When to split reads from writes with CQRS and when one model is enough, the numbers that change the answer, and what breaks with each.
Asked at: SDE-2Senior
The decision
A single model uses the same tables to accept changes and to answer screens. Command Query Responsibility Segregation (CQRS) splits the two jobs. A write model keeps the rules and the source of truth. A read model is a copy shaped for one screen, and code called a projector fills it from the changes. You are choosing between "simple and always right" and "fast reads, paid for with a second copy that is a little behind".
Choose CQRS when
- One screen needs a join over many tables. After an index, a copied column and a follower, it still uses more than half of a database server (Assumption: our own limit).
- Reads outnumber writes by more than 10 to 1, and three or more screens need three different shapes of the same data.
- Reads and writes need different storage, such as a relational database and a search engine.
- Users can accept a delay of about 1 second, or you can hide it by showing the user's own change from the save response.
- A team can own the projector, its alarms and its on-call work.
Choose one model when
- Reads and writes are both small. A server below 30 percent CPU (Assumption) does not need a second system.
- The user must see a change at once, such as a balance after a transfer, and you cannot hide the delay.
- Most screens read one row by key, such as a profile page. A read model would save no work.
- The team is small. Each extra part is one more thing to watch.
- Cheaper steps are not used up yet: an index, a copied column changed in the same transaction, a follower, a cache.
The numbers that change the answer
| Number | Flips toward CQRS when | Label |
|---|---|---|
| Reads per write | above 10 to 1 | Assumption (our own limit) |
| Cost of the heaviest screen after cheap fixes | above 50 percent of one server | Assumption (our own limit) |
| Delay users accept | 1 second or more | Assumption |
| Screens that need different shapes | 3 or more | Assumption |
A worked example for a food app at its evening peak (all numbers are assumptions):
- One database server has 8 cores, which give 8,000 ms of CPU per second.
- "My orders" is read 1,000 times per second. The join query costs 6 ms. The app writes 100 orders per second at 3 ms each.
- A lookup in a read model costs 0.3 ms. Each order makes 4 events.
One model. Reads: 1,000 x 6 ms = 6,000 ms, which is 75 percent of the server. Writes: 100 x 3 ms = 300 ms, which is 3.75 percent. Total: 78.75 percent. The reads are 1,000 / 100 = 10 per write, exactly at the line.
Cheap step first. Keep a copy of each user's 20 newest orders in a table in the same database, changed in the same transaction. Assumption: a read of this copy costs 1 ms. Then 1,000 x 1 ms = 1,000 ms, which is 12.5 percent. Assumption: the copy adds 1 ms to each write, which is 100 x 1 ms = 100 ms, so we ignore it here. There is no delay and no new system. Reads alone reach half a server at 8,000 ms x 50 percent / 1 ms = 4,000 reads per second.
CQRS in a separate store. Reads: 1,000 x 0.3 ms = 300 ms on the read store. The write side adds one outbox row per order (Assumption: 0.5 ms). The total is 300 ms + 100 x 0.5 ms = 350 ms, which is 4.4 percent. The projector handles 100 x 4 = 400 events per second. Assumption: one projector handles 1,000 per second, so it is 40 percent busy.
The projector stops for 10 minutes. The backlog is 400 x 600 seconds = 240,000 events. It shrinks by 1,000 - 400 = 600 per second, so catching up takes 240,000 / 600 = 400 seconds, which is 6.7 minutes.
A rebuild. Assumption: 2,000,000 orders per day for 90 days is 180,000,000 orders. Ten workers at 2,000 rows per second give 20,000 per second. The rebuild takes 180,000,000 / 20,000 = 9,000 seconds, which is 2.5 hours.
So at 1,000 reads per second the cheap copy wins. A separate store starts to pay above about 4,000 reads per second, or when you need a different kind of store.
What breaks with each choice
One model.
- A heavy read and a write fight for the same CPU. A slow report slows checkout.
- Every index you add for a screen makes every write slower.
- What you do: add a follower, an index for the hot query, and a cache.
CQRS.
- Lag. The user presses "Place order" and the new order is missing from the list. Show the new order from the save response, or read that user's own orders from the write side for 30 seconds.
- A wrong read model. A projector bug writes bad data to every user. Keep a rebuild job and a nightly compare job.
- Duplicates. Delivery is at least once. The projector saves the number of the last event it applied and skips repeats.
- A poison event. One bad event can block the stream. Send it to a dead letter queue and alarm on it.
What it costs
All numbers here are assumptions.
- Money: a second store and a log, about two or three more servers.
- People: four weeks for the first read model, then on-call work for the projector.
- Time to change your mind: adding CQRS to one screen takes weeks. Removing it takes days. The write tables were always the truth, so point the screen back to them.
How each level answers
The question is: "The orders screen is slow. What do you do?"
SDE-1: CQRS means separate models for commands and for queries. A read model is a copy shaped for one screen.
SDE-2: I add an index and a copied table in the same transaction first. If the screen is still heavy, I fill a read model from events and show the user's own order from the save response.
Senior: At 1,000 reads per second the join uses 75 percent of a server, and a same-database copy cuts that to 12.5 percent. I move to a separate store only above about 4,000 reads per second. The failure is a late or wrong read model, so I alarm on lag and keep a 2.5 hour rebuild as the fallback.
Staff: A second system costs about four weeks plus on-call work. So a slow screen must hurt a business number, such as orders lost on a slow page. I ship the copy first. Then I run a projector in shadow mode for a week, so it writes but nobody reads it. I switch one screen at a time with a flag. The screen team owns the projector. After 30 days I check lag and mismatches, and I roll back if mismatches are not near zero.
Say it in the interview
I would keep one model until the heaviest screen uses half a server after an index and a copied table. At 75 percent today, I would copy inside the database first and move to a read model above about 4,000 reads per second. The risk is lag and a wrong copy, so I show the user's own change from the save response and keep a rebuild job.
Common mistakes
- Adding CQRS to every design because it sounds senior. A screen without a problem still pays for a projector and a lag. Fowler warns that CQRS adds risky complexity for most systems.
- Believing CQRS needs event sourcing or two databases. It does not. A second table in the same database is a start. Event sourcing is a separate choice.
- Forgetting the user's own write. A user who cannot see the order that they just placed will place it again.
- Having no rebuild plan. A read model with a bug must be rebuilt. Without a plan, the fix takes days.
Sources
- Martin Fowler, "CQRS" (separate models for update and read; for most systems it adds risky complexity; use it for parts of a system): https://martinfowler.com/bliki/CQRS.html (opens in a new tab) (checked 26 Oct 2026).
- Microsoft Azure Architecture Center, "Event Sourcing pattern" (read models are fed by events, eventual consistency, event handlers must handle repeats): https://learn.microsoft.com/en-us/azure/architecture/patterns/event-sourcing (opens in a new tab) (checked 26 Oct 2026).
- The traffic, costs, times and team sizes are assumptions made for teaching. The other numbers are calculations shown above.
Understand the trade-off. Practise explaining your choice.
In an AI mock interview, the interviewer can push on a choice like this one: it changes a number and asks if your answer still holds.