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 suddenly customers can't check out. It feels like a coincidence, but it almost never is. The two events are wired to the same thing, and once you 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 you have customers tapping Buy — quick little writes, a new order every few seconds. On the other side you have the CEO opening a dashboard that asks, in effect, "show me everything that happened this quarter."
Both of those go to the same database. And that's the whole problem in one sentence: a single database is being asked to do two jobs that pull in completely opposite directions. Writing an order is small and fast. Answering a giant report is slow and greedy. They don't share nicely.
02 / 06
Why a report can quietly lock the door
Here's the part that trips people up. When that big report runs, it doesn't just read the data — it often locks the rows it's reading so the numbers can't shift halfway through counting. That's the database being careful: you don't want a total that changes mid-calculation.
But careful has a cost. While those rows are locked, your customer's checkout tries to write to the very same rows — and it can't. So it waits. The order doesn't fail, it doesn't error, it just sits there spinning while a ten-minute report finishes. To the customer it looks like your site is broken. To your logs it looks like everything is fine. That gap 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 simple instruction: stop making one database do both jobs.
Split it in two. A Write DB whose only concern is taking new orders — no reports allowed to touch it, ever. And a Read DB whose only concern is answering questions: reports, dashboards, search, anything that reads. The command side (things that change data) and the query side (things that ask about data) each get their own home. That separation 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 know about a new order?
The Write DB copies its changes over to the Read DB in the background. An order comes in, gets saved, and a moment later a quiet sync carries that same order across to the read side. Nobody waits for this copy to happen — it runs behind the scenes, out of the customer's way. The important thing to notice is that word moment: the copy is not instant.
05 / 06
What 'eventually consistent' actually feels like
Let's follow one real order to see why that delay is fine.
A customer taps Buy on order 42. The Write DB saves it in about ten milliseconds and the checkout screen immediately says Order confirmed. The customer is done and happy — and crucially, nothing the CEO is doing could have slowed that down, because the report never touches the Write DB.
A second later, order 42 syncs across into the Read DB. Now it shows up in reports too. For that one-second window the two databases disagree slightly — the order exists on the write side but not yet on the read side. That's called being eventually consistent, and for a shop it's completely acceptable. A sales dashboard being 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, glow red, take its ten minutes — it doesn't 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's CQRS: not a clever algorithm, just a decision to stop asking one thing to be good at two opposite jobs at the same time.
The short version
- A single 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, and your checkout write blocks behind that lock, so it hangs instead of failing.
- CQRS = split into a Write DB (orders only) and a Read DB (reports/queries only).
- The Write DB syncs to the Read DB in the background, so the read side is 'eventually consistent' — usually a second or so behind.
- That tiny delay is a fine trade: a dashboard being one second stale is harmless; a frozen checkout is not.