Sign in
TodayMapLearnPracticeReview
Library
13 MINexpertCloud ArchitectureNot started

Event Sourcing

Event Sourcing stores the system's state as an append-only log of events — every state change is captured as an immutable event, and current state is derived by replaying the log. Instead of storing the current row in `orders`, you store `OrderCreated`, `OrderShipped`, `OrderCancelled` events; the order's current state is computed by folding over the events. This gives an audit trail for free, time-travel queries (what was the state at 3 PM Tuesday?), and the ability to rebuild any read model from the log. The trade-offs: complexity, eventual consistency on reads, and an event schema that must be carefully versioned.

Why this matters

Traditional CRUD stores only the current state — overwriting history with every update. When a bug occurs, you can't reconstruct what happened. When you need a new view of the data, you can't derive it from current state alone. Event Sourcing inverts this: the log is the truth, current state is a projection. This is the architecture behind financial ledgers (every transaction is an event), Git (every commit is an event), Kafka (every message is an event), and modern CQRS systems. It's also the foundation for audit-compliant systems, time-travel debugging, and rebuilding read models from scratch.

Prerequisites
  • CQRS
  • Write-Ahead Log (WAL)
Related
  • CQRS
  • Materialized View
Used in
  • CQRS
Lesson

How it works

In a traditional CRUD system, the database stores the current state of each entity. When you update an order's status from pending to shipped, the old pending value is overwritten — history is lost. To query ‘what was this order's status at 3 PM Tuesday?’ you'd need separate audit logs, and they're usually incomplete.

Event Sourcing flips the model. The system's state is an append-only log of events:

code
event_id  | aggregate_id | event_type        | payload              | timestamp
1         | order-42     | OrderCreated      | {items: [...], total} | 10:00
2         | order-42     | PaymentReceived   | {amount: 100}         | 10:05
3         | order-42     | OrderShipped      | {tracking: '...'}     | 11:30
4         | order-42     | OrderDelivered    | {}                    | 14:00

The order's current state is computed by replaying events 1-4: created, paid, shipped, delivered. The current state is a projection of the log.

Key properties:

  • Append-only — events are never modified or deleted (except via compaction, which preserves the latest event per aggregate).
  • Immutable — once written, an event never changes.
  • Ordered — events have a sequence number; replay must preserve order.
  • Named and typed — each event type describes a domain fact (OrderShipped, not update status to 'shipped').
  • Source of truth — the log is the truth; projections (read models) are derived.

The aggregate (e.g., an Order) is reconstructed by folding events:

python
def apply(state, event):
    if event.type == 'OrderCreated':
        return State(status='pending', items=event.items, total=event.total)
    elif event.type == 'PaymentReceived':
        return state.with(status='paid')
    elif event.type == 'OrderShipped':
        return state.with(status='shipped', tracking=event.tracking)
    ...

current_state = fold(apply, initial_state, events_for_order_42)

This is the same pattern Git uses internally (commits are events), and the same pattern financial ledgers have used for centuries (every transaction is recorded).

Benefits of Event Sourcing:

  • Perfect audit trail — every state change is recorded with timestamp, who, what, why. Required for finance, healthcare, regulated industries.
  • Time-travel queries — replay events up to a point in time to see the state then. ‘What was the account balance on Jan 15 at noon?’
  • Rebuildable read models — any projection can be rebuilt from scratch by replaying the log. Schema changes are easy: write a new projection, replay.
  • Decoupled read models — different projections serve different query patterns, all from the same log. CQRS reads naturally fall out.
  • Bug diagnosis — when a bug occurs, replay the events that led to the bad state and see exactly what happened.
  • Event-driven integration — other services can subscribe to the event stream and react ( Saga pattern, pub/sub ).
  • No update anomalies — there are no updates, only appends. Optimistic concurrency control is natural: each event references the prior version.

Costs:

  • Complexity — versioning events, writing projections, handling replays, dealing with side-effects (e.g., sending an email during replay) is hard.
  • Eventual consistency on reads — projections lag the log by milliseconds (or more under load).
  • Storage growth — the log grows forever; compaction (keeping only the latest event per aggregate) is needed.
  • Schema evolution — events are immutable, but their schema evolves. Versioning, upcasting, and backward compatibility are required.
  • Side-effects are hard — during a projection rebuild, you must NOT re-send emails or charge credit cards. Idempotency and outbox patterns are essential.
  • Learning curve — developers must think in events, not state.
Event Schema Evolution

Events are immutable, but their schema evolves. After 6 months in production, you'll want to add a field to OrderCreated, rename a field, or split one event into two. Patterns: (1) versioned events — OrderCreatedV1, OrderCreatedV2; consumers handle both. (2) upcasters — transform old events to the new schema on read; the log stays raw, but consumers see the new shape. (3) weak schema — Avro/Protobuf with backward-compatible field additions. (4) snapshot and migrate — read all old events, write new ones in a new format. Whatever you choose, plan for evolution from day one. Events are forever; their schemas must evolve gracefully.

Performance: replaying 10,000 events to reconstruct an aggregate's state is slow. Two optimizations:

1. Snapshots — periodically persist the current state as a snapshot. To reconstruct, load the latest snapshot and replay only the events after it. Common snapshot cadence: every 100 or 1,000 events. The snapshot is itself derived from the log (it's not the source of truth — the log is).

2. Compaction — for long-lived aggregates (e.g., a customer over 10 years), keep only the latest event per type per aggregate, or fold old events into a single ‘summary’ event. This reduces log size at the cost of historical fidelity.

Projection rebuilds are similar: for a large projection (e.g., daily_revenue over years), you can either (a) replay all events from the beginning (hours for billions of events) or (b) snapshot the projection periodically and resume from the snapshot.

A common operational pattern: keep the raw event log forever (or for the regulatory retention period), maintain snapshots and projections alongside, and rebuild projections as needed when schemas change. The log is canonical; snapshots and projections are derived and rebuildable.

Check yourself
interview

In an event-sourced system, what is the source of truth, and how is current state derived?

Pick one answer.

Check yourself
interview

You need to rebuild a read-model projection after a schema change. How does Event Sourcing make this possible, and what's the main concern?

Pick one answer.

Engineering mental model

Mental model. Think of Event Sourcing as a deliberate boundary in a system. The boundary exists because something becomes harder to manage when everything is done in one place: latency, scale, failure isolation, consistency, cost, or team ownership. The useful question is not “what does Event Sourcing mean?” but “what pressure makes this boundary worth introducing, and what new failure mode does it create?”

Design lens

Before choosing Event Sourcing, name the workload, the critical user path, the dominant bottleneck, the failure you are trying to absorb, and the trade-off you are willing to accept. If you cannot name those five things, the design is probably premature.

Original NO CAP systems visual for Event Sourcing.
Image unavailable. Original NO CAP systems visual for Event Sourcing.
Event Sourcing: a compact system-thinking visual.— Original NO CAP visual.
message_id = queue.publish({
    "type": "event-sourcing",
    "key": resource_id
})
# Consumer must be safe to retry.
A minimal engineering sketch for reasoning about Event Sourcing.

Back-of-the-envelope reasoning

Numerical lens: write down traffic, payload size, read/write ratio, peak multiplier and durability target before choosing a component. The numbers should justify the architecture.

Interactive sandboxdeterministic

Interactive thought experiment: Event Sourcing

Change the variables below and predict what breaks first in Event Sourcing. The production lab can later reuse these same inputs.

System pressure6%
Queue backlog growthstable
Try this

Change one variable at a time. Predict the failure mode first, then move the slider and see whether your mental model matches the simplified system response.

Hint

If you are stuck on Event Sourcing, start by drawing the request path and marking every network hop, stateful component, queue, cache and failure boundary. Then estimate where the system will saturate.

Check yourself
solid

You increase traffic by 10× in a system using Event Sourcing. What should you inspect first?

Pick one answer.

Check yourself
interview

Which statement is the safest engineering habit when using Event Sourcing?

Pick one answer.

Try this
interview

You have dashboards for traffic, latency, errors and saturation. You can change the architecture, but every change has operational cost.

Production scenario: your system uses Event Sourcing, traffic suddenly spikes, and p99 latency doubles. What is your first move?

Interview drill

Answer this without notes: When would you choose Event Sourcing, and when would you intentionally avoid it? Mention at least one bottleneck it addresses, one failure mode it introduces, and one alternative. Then quantify the workload you are designing for.

Engineering lens

For Event Sourcing, separate producer speed from consumer speed. The key design question is what happens when production temporarily exceeds processing capacity: queue it, shed it, slow producers down, or degrade the feature.

Numerical sanity check

A simple queue sanity check: if producers create 8,000 messages/s and consumers process 6,000 messages/s, backlog grows at roughly 2,000 messages/s until the imbalance is corrected.

Check yourself
interview

Do not optimize for a memorized definition. Reason from the workload and failure mode.

A downstream service slows down while Event Sourcing keeps accepting traffic. Would you rather apply backpressure, queue more work, shed load, or degrade? Explain the trade-off.

Pick one answer.

Trade-offs

What you gain, what you pay

Pros
  • +Perfect audit trail — every state change recorded with full context.
  • +Time-travel queries — replay up to any point in time.
  • +Rebuildable read models — any projection can be rebuilt from the log.
  • +Multiple projections from one log — CQRS reads fall out naturally.
  • +Bug diagnosis — replay the events leading to a bad state.
  • +Event-driven integration — other services can subscribe to the stream.
  • +No update anomalies — only appends, so optimistic concurrency is natural.
Cons
  • −Complexity — versioning events, writing projections, handling replays.
  • −Eventual consistency on reads — projections lag the log.
  • −Storage growth — the log grows forever; needs compaction.
  • −Schema evolution is hard — events are immutable but their schema evolves.
  • −Side-effects are tricky — must not re-trigger them during replay.
  • −Steep learning curve — developers must think in events, not state.
Failure modes

How this breaks in production

  • Event schema evolves incompatibly — old events can't be replayed with new code; requires upcasters or versioned events.
  • Projection lag grows under load — read models fall behind writes.
  • Side-effects re-triggered during replay — emails re-sent, cards re-charged; must use outbox or idempotency keys.
  • Snapshot corruption — a bad snapshot requires full replay to recover.
  • Event ordering bugs — out-of-order events produce wrong state; needs sequencing or version vectors.
  • Unbounded log growth — without compaction, storage costs grow forever.
Common mistakes

Don't fall into these traps

  • •Treating events as CRUD updates — naming events `OrderUpdated` instead of `OrderShipped` loses domain meaning.
  • •Storing current state in the event payload — events should describe what happened, not the new state.
  • •Not versioning events from day one — schema evolution becomes painful later.
  • •Triggering side-effects inside projection logic — replays re-trigger them.
  • •Not snapshotting long-lived aggregates — replaying thousands of events per read is slow.
  • •Forgetting idempotency — replays and redeliveries cause duplicate effects without it.
Where you see it

Real systems using this

Financial ledgers — every transaction recorded, never deleted.Git — every commit is an event; current tree state is derived.Kafka-based event-driven systems — topics as event logs, consumers as projections.Domain-Driven Design aggregates — aggregates apply events, persist to event store.Compliance-heavy systems — healthcare, finance, where full audit trails are required.CQRS systems where the write model is the event log.
Teardowns

How real systems implement this

  • Apache Kafka as an event log — Kafka's design is built around the event log concept — topics are append-only, partitioned logs; consumers are projections. Many CQRS systems use Kafka as the event store, with consumers (Flink, Kafka Streams, custom services) building read models.
  • EventStoreDB — A purpose-built event store that implements Event Sourcing natively — append-only streams per aggregate, subscriptions for projections, built-in snapshot support. Used in DDD-heavy systems where events are first-class.
  • Axon Framework (Java) — A Java framework that implements CQRS + Event Sourcing explicitly: aggregates apply events, events are persisted to an event store, query-side projections subscribe and build read models. The canonical reference implementation for the pattern.
  • Git internals — Git's commit log is event-sourced — every commit is an immutable event, the current tree state is derived by replaying commits. Branches and tags are projections. This is why Git can rebase, cherry-pick, and time-travel.
Interview prompts

Practice saying it out loud

  • Q1What is Event Sourcing, and how does it differ from traditional CRUD?
  • Q2How do you rebuild a read-model projection in an event-sourced system? What are the concerns?
  • Q3How do you handle event schema evolution over years in production?
  • Q4Why is Event Sourcing popular in financial systems? What does it give you that CRUD doesn't?
  • Q5What are the trade-offs of Event Sourcing? When would you NOT use it?
Research

Further reading & references

System Design Primer
Open source
ByteByteGo — Scale from zero to millions
ByteByteGo
System Design Tutorial
GeeksforGeeks
System Design Roadmap
roadmap.sh
Cloud Architecture reference
Reference
Cloud Architecture reference
Reference
Cloud Architecture reference
Reference

Core explanations are original NO CAP material. External references are provided for deeper study and standards.

What next?

Mark as understood once the mental model clicks.

Next recommended

CQRS