system design
Why Your Checkout Freezes When the Boss Runs a Report (and How CQRS Fixes It)
Someone in finance opens a big dashboard, and checkout stops working for everyone. It looks like a coincidence. It almost never is. The two things are wired to the same box, and once you can see the wire, the fix is obvious. That fix has a name that sounds scarier than it is: CQRS.
01 / 06
One database, two very different jobs
Picture a small online shop. On one side, customers tapping Buy a few times a second: tiny, fast writes. On the other, the CEO opening a dashboard that asks, in effect, show me everything that happened this quarter.
Both land on the same database. That is the whole problem in one sentence. A single database is being asked to do two jobs that pull in opposite directions. Saving an order is small and quick. Answering a giant report is slow and greedy. They do not share the table nicely.
02 / 06
Why a report can quietly lock the door
Here is the part that catches people out. A big report does not just read the data. It often locks the rows it is reading, so the totals cannot shift halfway through counting. That is the database being careful, and being careful is usually right: you do not want a number that changes mid-calculation.
But the lock has a cost. While those rows are held, a customer's checkout tries to write to the very same rows and cannot. So it waits. The order does not fail and it does not error. It just sits there spinning while a ten-minute report finishes. To the customer the site looks broken. In your logs everything looks fine. That gap between the two is exactly why this bug is so maddening to chase.
03 / 06
The idea behind CQRS
CQRS stands for Command Query Responsibility Segregation, which is a lot of syllables for a small instruction: stop making one database do both jobs.
Split it in two. A Write DB whose only concern is taking new orders, with no reports allowed to touch it. And a Read DB whose only concern is answering questions: reports, dashboards, search, anything that reads. Commands change data. Queries ask about it. Each side gets its own home.
The name comes from Greg Young, who built it on an older rule from Bertrand Meyer called Command Query Separation: a single method should either change something or answer something, never both. CQRS takes that idea and applies it to whole databases instead of single methods. That is the entire pattern. Everything else is detail.
04 / 06
How the two databases stay in sync
Splitting them raises the obvious question. If orders land in the Write DB but reports read from the Read DB, how does the Read DB ever hear about a new order?
The Write DB copies its changes across to the Read DB in the background. An order comes in, gets saved, and a moment later a quiet sync carries that same order to the read side. Nobody waits for the copy. It runs behind the scenes, out of the customer's way.
Notice the word moment. The copy is not instant, and that turns out to matter.
05 / 06
What 'eventually consistent' actually feels like
Follow one real order to see why the delay is fine.
A customer taps Buy on order 42. The Write DB saves it in a few milliseconds and the screen says Order confirmed. The customer is done and happy. Nothing the CEO is doing could have slowed that down, because the report never touches the Write DB.
A second later, order 42 syncs into the Read DB and starts showing up in reports too. For that one-second window the two databases disagree: the order exists on the write side but not yet on the read side. That state has a name, eventually consistent, and for a shop it is completely fine. A sales dashboard that is one second behind reality has never hurt anyone. A checkout that hangs for ten minutes absolutely has.
06 / 06
Why nobody's checkout waits anymore
Now the CEO can run the heaviest, slowest, most row-locking query imaginable, and it lands entirely on the Read DB. That database can strain and glow red and take its ten minutes. It does not matter, because no customer is trying to write there. Meanwhile the Write DB stays calm and fast, doing the one job it was left with.
Two databases, one job each. The report and the checkout stopped fighting because they stopped sharing a table. That is CQRS. Not a clever algorithm. A decision to stop asking one thing to be good at two opposite jobs at the same time.
The short version
- One database serving both orders (writes) and reports (reads) is the root cause: the two workloads fight over the same rows.
- Big reports lock rows while counting, so your checkout write blocks behind the lock and hangs instead of failing.
- CQRS splits it into a Write DB (orders only) and a Read DB (reports and queries only).
- The Write DB syncs to the Read DB in the background, so the read side is 'eventually consistent', usually about a second behind.
- That tiny delay is a good trade: a dashboard one second stale is harmless, a frozen checkout is not.
- The name is Greg Young's, built on Bertrand Meyer's older Command Query Separation rule.