0 XP · 0 scenes

CQRS and event sourcing

Architecture Patterns · lesson 6 of 7 · about 30 min

CQRS and event sourcing

Keeping the model you write separate from the model you read, and storing what happened instead of the latest state: when each pays off, what a rebuild costs, how to handle concurrent commands and duplicate events, and why replaying history must never re-send the e-mails. Tap any for a plain-language card.

0 / 9 sections done
  1. 1Split reads from writes4 min
  2. 2The log is the state6 min
  3. 3A new view from history4 min
  4. 4Two commands at once4 min
  5. 5Hearing events twice3 min
  6. 6Which pattern?2 min
  7. 7Who lives by this1 min
  8. 8Interview angle3 min
  9. 9Break It: the replay3 min

Words in this lesson8Tap one for its meaning.

One model, two jobs

The tables that keep an order correct are not the tables that make the order page fast. Why force one design to do both?

It pays off when reads far outnumber writes, queries are expensive, or different screens need very different shapes (a list, a search, a dashboard). It does not pay off for plain CRUD with simple queries: you would only be adding moving parts. The break-even in this simple cost model is when reads per write exceed read models ÷ (joins − 1).

1,000 reads and 10 writes a second with 5-table joins and 3 read models: one model costs 5,010 units a second, the split costs 1,040. The split wins whenever reads per write exceed 0.75 here.

  • One shared model
    5,010 units/s
  • CQRS: write model + read models
    1,040 units/s
Reads per write now100.0
Break-even reads per write0.75
Which is cheaperCQRS

At 1,000 reads and 10 writes a second, splitting costs 1,040 units against 5,010, because each read becomes one lookup instead of 5 table reads.

Illustrative cost model: one model = reads × joins + writes; split = reads + writes × (1 + read models). Real costs differ per query, but the shape of the trade is this.

History is the data

The balance says ₹13,640. How did it get there, and what was it last Tuesday? A table that only stores the latest value cannot say.

Events are facts and are never edited. A mistake is fixed by appending a new event that corrects it (a refund), which is exactly how accounting ledgers have always worked. The cost is replay: rebuilding state from millions of events is slow, so systems save snapshots every so often and replay only what came after. Try the two buttons, then drag the time slider.

With 10 million events in a stream and replay at 20,000 events a second, a full rebuild takes about 8 minutes; with a snapshot every 1,000 events it takes about 55 ms.

#19 · day 37 · Deposit ₹1500
#20 · day 39 · Deposit ₹900
#21 · day 40 · Service fee ₹10
#22 · day 41 · Deposit ₹1200
#23 · day 43 · Withdraw ₹300
#24 · day 44 · Deposit ₹1100
Balance as of #24₹13,640
Balance now₹13,640
Events in the log24
Rebuild with no snapshot8 min
Rebuild from latest snapshot55 ms
Snapshots stored10,000

The balance as of event #24 was ₹13,640, and now it is ₹13,640, and nothing was overwritten to get there: 24 events, 1 fees, 6 withdrawals. A stream of 10,000,000 events would take 8 min to replay in full, or 55 ms from a snapshot.

The balance is computed by really folding the log shown. Replay speed (20,000 events/s per stream) is an assumed figure for the rebuild times.

A new view from old events

Product wants a new dashboard that needs data from the last five years. With an event log you can build it, but how long until it is current?

If replay is not clearly faster than the live rate, the new view never catches up. Rebuilds are throttled to protect production, or run in parallel by stream, or start from a snapshot. Plan for the case where this takes hours or days, and keep the old read model serving until the new one has caught up, then switch.

1 billion historical events, replay at 50,000 a second and 2,000 new ones arriving each second: the new view is current after about 5.8 hours. If replay were slower than 2,000/s it would never catch up.

Backlog shrinks by48,000/s
Time until the new view is current5.8 h

Replaying 1,000 million events at 50,000/s while 2,000/s keep arriving, the backlog shrinks by 48,000/s and the new view is current after 5.8 h.

Time = backlog ÷ (replay speed − live rate). A deliberately simple model: replay speed in practice depends on the projection's database writes.

Who wins when two commands race?

Two people withdraw from the same account in the same instant. Each read the balance first. Both cannot be right.

Conflicts depend on how often commands hit the same stream while a handler is working. Spread over millions of accounts it is rare; aimed at one hot stream (a flash-sale stock counter, a global sequence) it collapses. If a stream is that hot, reshape it: split it into smaller aggregates, or serialise commands through a queue for that key.

At 1 command a second per stream and a 100 ms handler about 91% succeed first time; at 5 a second only 53% do and about 4% fail after 3 retries; at 20 a second more than half fail.

  • Succeed on the first try
    53%
  • Needed a retry
    47%
  • Gave up (failed)
    3.6%
Appends attempted per command1.78
Commands that eventually succeeded96%

At 5 commands a second on one stream with a 100 ms handler, 53% of commands append first time, 47% hit a conflict and 3.6% give up after 3 retries. Each command makes 1.78 append attempts on average.

Simulated: 2,500 commands as a Poisson stream; an append fails if any other append landed since the command loaded the stream.

The read side hears everything twice

The projection adds each deposit to a running total. The message broker redelivers a few. Is anyone's balance still right?

That one habit also makes rebuilds safe, because replaying from the start just re-applies events the projection recognises. Ordering matters too: apply events for one entity in the order they were written (partition by entity id), and treat a gap in positions as a reason to wait, not to skip. Dial up the duplicate rate and compare.

10,000 deposits of ₹100 with 2% delivered twice: the naive projection overstates the total by ₹19,300 (193 duplicates); the idempotent one is exactly right.

  • Truth
    ₹1,000,000
  • Naive projection
    ₹1,019,300
  • Idempotent projection
    ₹1,000,000
Messages delivered10,193
Duplicates193
Naive projection is wrong by₹19,300

193 of 10,000 events arrived twice, so the naive projection shows ₹1,019,300 instead of ₹1,000,000 (₹19,300 too much). The idempotent projection remembers event ids and is exactly right.

Seeded simulation of at-least-once delivery. The idempotent projection keeps the set of applied event ids (in production, the last applied position).

Which pattern?

Six systems, four designs.

CQRS and event sourcing are separate ideas that are often combined; most systems need neither. Pick the simplest design that fits. Each answer is worth 20 XP.

Ask: is the problem reads, history, or neither?

Question 1 / 6

An internal admin form with a few hundred users and simple lists.

Tip: press 1 to 4 to answer, Enter for the next one.

Who lives by this

From Greg Young to LMAX.

Published sources on CQRS and event sourcing in practice.

Naming the patternsGreg Young and Martin Fowler

CQRS, event sourcing

Greg Young popularised CQRS and event sourcing in the late 2000s as practical patterns for complex domains, and Martin Fowler's writing documented them, with the repeated warning that CQRS adds risky complexity and suits only some parts of a system.

A single-threaded coreLMAX Exchange

event-sourced in-memory processor

The LMAX trading platform, described by Martin Fowler in 2011, kept business state in memory and rebuilt it from a journal of input events, reporting throughput of around six million orders a second on a single thread.

The log as the backboneLinkedIn and Apache Kafka

the log as a source of truth

Jay Kreps' essay 'The Log' described how an ordered, replayable log of changes can feed many derived systems, the idea behind Kafka and a practical base for building projections.

Documented patternsMicrosoft Azure Architecture Center

CQRS and Event Sourcing patterns

Microsoft's published design patterns describe CQRS and event sourcing together with their trade-offs, including eventual consistency of read models, replay cost and the need for idempotent consumers.

Interview angle

What interviewers actually ask.

Six questions, each worth 20 XP. Strong answers say when NOT to use these patterns.

Name the benefit (reads, history) and the cost (lag, complexity, replay).

Question 1 / 6

When does splitting read and write models pay off?

Tip: press 1 to 3 to answer, Enter for the next one.

Break it

You fix a projection bug and replay the whole history. Overnight, two million customers get “Your order has shipped!” again.

Keep them apart. Build projections from replay-safe code that touches only your own store. Run reactions as separate consumers that are not part of rebuilds, or that carry an idempotency key per event and check it before acting, or that skip events older than a cutoff. Treat a replay like a deployment: a switch that must be on by design, never an accident.

40 million events with 5% that send an e-mail: an unguarded replay sends 2 million e-mails. With side effects switched off for replay, or an idempotency check on the sender, it sends none.

Events replayed40,000,000
Events that trigger an e-mail2,000,000
Duplicate e-mails actually sent2,000,000

Replaying 40,000,000 events re-ran 2,000,000 e-mail reactions and sent 2,000,000 duplicate messages to customers, none of which can be recalled.

Arithmetic, not a measurement: duplicates = events × share that trigger a reaction, when the reaction runs during replay.

Where to go next