skip to content

questions

6

In an event-sourced system, what is a 'projection' and why do we build one instead of querying the raw event log directly for reads?

level: juniorimportance: must knowfreq 75%

answer

  1. fold over events
  2. projector + checkpoint
  3. read model is not the source of truth
  4. amortize replay cost
  5. eventual consistency lag

basics

~20 s

A projection is a separate copy of your data, built by reading through the history of every change (events) and turning it into a simple, fast-to-search table - like building an index from a diary instead of re-reading the whole diary every time you need an answer.

solid answer

~40 s

A projection is a read model: code called a projector subscribes to the append-only event stream and applies a fold function, where each event type updates some external store - a SQL table, document store, or cache - into the shape a query actually needs. We don't query the raw log directly because it's an ordered sequence of immutable facts with no indexes for arbitrary lookups; replaying thousands of events per request would be far too slow. Materializing state ahead of time into a purpose-built store makes reads cheap and lets the read side scale and evolve independently of the write side.

go deeper

for a junior

Should describe, at a basic level, that a projection is a table built from events, and give the intuition that querying pre-built state is faster than replaying history.

for a middle

Should describe the projector loop precisely (subscribe, read in order, apply handler, persist checkpoint) and explain why the log itself isn't queried directly.

for a senior

Should discuss the consistency trade-off explicitly (eventual vs strong), name concrete failure modes like lag and non-idempotent handlers, and connect design choices (which store, which shape) to the consuming query's needs.

for a principal

Should reason about running many projections off one log at organizational scale - ownership boundaries, checkpoint isolation, backpressure on the source store, and when the operational cost of a projection isn't justified by the query need.

## What the write model actually stores An event-sourced write model doesn't persist 'current state' directly; it persists the full history of facts that produced that state, as an **append-only, strictly ordered, immutable log of domain events** - things like `OrderPlaced`, `ItemAdded`, or `PaymentCaptured`. Each event records something that already happened and is never edited or deleted after the fact. ## What a projector does A **projection** is the mechanism that turns this history into something a query can use cheaply: a projector process subscribes to the event stream, reading events one at a time in the order they were appended, and for each event type it applies a small handler function that mutates an external store: - inserting a row on `OrderPlaced` - incrementing a counter on `ItemAdded` - flipping a status column on `PaymentCaptured` The projector also persists its own read position (a **checkpoint**, often an event number or stream position) so that if it restarts, it resumes from where it left off instead of reprocessing the entire log or, worse, skipping events. ## Why the raw log is the wrong thing to query The reason this exists rather than just querying the event log for reads is mostly about shape and cost. The log is optimized for one thing: fast, ordered appends and sequential reads from a given position - it is not indexed for 'give me all orders placed by customer X in the last 30 days with status Y'. Answering that from raw events would mean scanning and replaying a large slice of history on every request, which is both slow and wasteful, since the same computation (the **fold**) would be repeated for every query instead of once per event. A projection amortizes that cost: the fold runs once, incrementally, as each event arrives, and the result sits in storage shaped exactly for the query. | Materialized shape | Serving | |---|---| | a flat SQL table | reporting | | a document per aggregate | a UI | | a search index | full-text lookup | Because reads and writes are physically decoupled, the read side can be scaled, cached, indexed, and even swapped out (SQL today, a search engine tomorrow) without touching how events are written, and multiple projections can be built from the same log for different consumers without coordination between them. ## The trade-off The unavoidable trade-off is consistency. - A normalized, transactional table gives you **strong consistency** - a write and the very next read in the same transaction always agree. - A projection is asynchronous and therefore **eventually consistent**: there is a window, usually milliseconds but sometimes longer under load, where an event has been appended to the log but the projection has not yet applied it, so a read against the projection returns stale data. Teams pay for the query flexibility and read-side scalability with this lag, plus the operational cost of running and monitoring an extra moving part (the projector) and storing the data twice - once as events, once as materialized state. If a system truly needs read-your-writes guarantees everywhere, or has only trivial query needs, the extra machinery of a projection may not be worth it. ## How projections fail in production In production, projections fail in a few characteristic ways. 1. **Lag** is the most common: under a burst of write traffic, or after a slow downstream call inside the handler, the projector falls behind, and support tickets say 'I made a change and it's not showing up.' 2. **Crashes mid-batch** are the second: if a projector updates the read store and then crashes before persisting its checkpoint, on restart it may reprocess the last batch, and unless the handler is idempotent (safe to apply the same event twice) that can double-count values like balances or counters. 3. A third failure mode is **schema drift**: when the event schema evolves - a field renamed, a new event type introduced - but the projection's handler code isn't updated to match, it silently drops data or throws and stalls the whole subscription, which then blocks every event behind it. 4. Finally, an **unpersisted or corrupted checkpoint** forces a full replay from the start of the log, which on a large, years-old event store can take hours and hammer the source store's read throughput for every other consumer at the same time. ## A concrete example A concrete example: a banking system stores an append-only ledger of events like `AccountOpened`, `FundsDeposited`, and `FundsWithdrawn` as its source of truth. A customer-facing 'current balance' screen doesn't replay that ledger on every page load; instead, a projector maintains an `account_balances` table, applying +amount on `FundsDeposited` and -amount on `FundsWithdrawn`, updated within milliseconds of each event. Frameworks like **Axon Framework** (Java/Kotlin) formalize this with a `TrackingEventProcessor` that tracks its own position per projection, and **EventStoreDB** exposes native catch-up subscriptions for exactly this purpose - both are direct implementations of the pattern described here.

  • Can a single event log feed more than one projection at the same time?
    Yes - that's one of the main benefits: independent projectors can each subscribe to the same log and build different shaped read models (a search index, a reporting table, a cache) without touching the write side or each other. They just each track their own checkpoint position.
  • What happens to a projection if the projector process crashes and is restarted?
    It reads its last persisted checkpoint and resumes the subscription from there, replaying only events after that position. If the checkpoint was written before the read-store update was durably applied, the same event can be reprocessed, so handlers need to tolerate reapplication safely.
  • Is a projection the same thing as a cache?
    They're related but not identical: a cache typically stores a copy of data that already exists in queryable form elsewhere and can usually be dropped and lazily repopulated on a cache miss. A projection is the only queryable form of that data outside the event log - dropping it means rebuilding it by replaying potentially the entire history, not a cheap point lookup.

Like a bank's monthly statement: instead of re-reading every transaction receipt each time you want to know your balance, someone tallies the receipts once into a running total sheet you can glance at instantly.

saying these in an interview costs you the question

  • says projections keep the write model consistent by locking rows
  • claims reads always see the very latest event with no lag
  • thinks the raw event log itself is queried per API request in production
  • doesn't mention a checkpoint or position being persisted
  • assumes one projection is 'the' read model and multiple projections from one log is unusual

context

open as a page

When a brand-new projection needs to be built for a system that already has years of events in its log, how does a 'catch-up subscription' take it from zero to fully up to date, and then keep it live afterward?

level: middleimportance: must knowfreq 65%

basics

~20 s

It reads through the entire history of past events first, from the very beginning, building up the data step by step, and once it reaches the present, it switches to just listening for new events as they happen - like binge-watching a show to get caught up, then watching new episodes live.

open as a page

A user submits a form that appends an event to the log, then the client immediately navigates to a page that reads from a projection built off that log - and the change is missing. What is actually happening here, and what are the common ways to handle it?

level: seniorimportance: must knowfreq 70%

basics

~20 s

The projection hasn't caught up to the newest event yet, so the read is briefly stale - like refreshing a scoreboard a split second before it updates. Common fixes: make the client wait for the update, read the write directly instead of from the projection, or show a temporary version on screen immediately.

open as a page

A team discovers that a projection's event-handling logic has been silently miscalculating a customer's lifetime-spend column for six months. What are the mechanics of fixing this by rebuilding the projection from the event log, and what must the event handlers guarantee for that rebuild to produce correct results?

level: middleimportance: should knowfreq 50%

basics

~20 s

You fix the buggy code, wipe out the broken table, and replay every past event from the start through the corrected logic to rebuild it fresh - like erasing a wrong total and re-adding every receipt correctly this time.

open as a page

A projection's catch-up subscription redelivers a batch of already-processed events after the consumer crashes and restarts. What must the projection's event handlers do to avoid corrupting the read model on redelivery, and how does at-least-once delivery change how projection code should be written?

level: seniorimportance: should knowfreq 50%

basics

~20 s

The handler must be safe to run twice on the same event without messing up the data - for example, setting a value directly instead of always adding to it - because most event systems can redeliver an event after a crash rather than guaranteeing it's processed exactly once.

open as a page

A projection backing a high-traffic, customer-facing read API takes four hours to fully rebuild from the event log after a handler bug fix. What techniques let you deploy the corrected projection without taking that read API offline during the rebuild?

level: principalimportance: nice to knowfreq 30%

basics

~20 s

Build the new, corrected version in a completely separate table while the old one keeps serving traffic, and only switch customers over to the new one once it's fully caught up and checked - like renovating a store in a new building instead of closing the old one down for the day.

open as a page