A real application almost never keeps its data in one place: alongside the main database live a cache, a search index, an analytics store, denormalized tables, read models. It's easy to drown in this zoo and lose track of who owns what. There is a simple frame that brings order — splitting every store into a system of record and derived data. This is a conceptual article "for horizons": it is not about a specific technology, but about how to stop getting lost in your own architecture.
The source of truth versus its reflections
A system of record (the source of truth) is the store where a fact lives exactly once in authoritative form. Usually it is a normalized database: a user placed an order — the order is written here, and on any discrepancy with the other stores, the system of record is by definition right.
Derived data is the result of transforming data from the system of record: it can always be recomputed from the source. Almost everything else falls here:
- a cache — a fast copy of part of the data;
- a search index (Elasticsearch, GIN) — data repacked for text queries;
- a materialized view and a denormalized field — a precomputed result;
- a read model in CQRS — data laid out for a specific screen;
- an analytics store (ClickHouse) — the same events shaped for scans.
The defining trait of derived data is redundancy: it duplicates what already exists in the source, for the sake of read speed. The same thing can be reflected many ways, so there are often several derived sets per source.
The key property: derived data is disposable
The definition leads to a practical conclusion that changes how you treat data: derived data is disposable. Lost the cache, the index, the read model? No disaster: rebuild it from the source of truth and the system is good as new. Lose the system of record and that is a real disaster — there is nothing to restore from.
This dictates everything else: the source of truth is what you back up and protect first; every derived set must have a from-scratch rebuild procedure (reindex, warm the cache, rebuild the projection); and "the schema got lost after the rollout" stops being a catastrophe if it was merely a projection. Knowing which store is the source and which is derived is half of a clear architecture.
The three ways to produce derived data
How exactly does data flow from the source into derived sets? There are three processing modes, and it pays to tell them apart:
- Online (a service). Waits for a request and answers as fast as possible; the main measure is response time and availability. This is how the application itself works, but online is no good for deriving large sets.
- Batch. Takes a large input set, runs a job for minutes to hours, produces a result; the main measure is throughput, and it runs on a schedule (a nightly reindex, a mart recompute). The big-data classic is MapReduce/Spark; in the backend, simpler — a queue-based background worker.
- Stream. In between: reacts to events as they arrive, with low latency, but processes a stream of changes rather than the whole set. This is how derived data is updated in near-real time.
Batch rebuilds everything and is reliable but lagging; stream keeps derivatives fresh but is more complex. Choosing between them is a big topic of its own (Kleppmann's chapter 11).
The discipline: derive, don't dual-write
The main mistake with derived data is writing to it directly, bypassing the source. The classic example is a dual write: the application writes to both the database and the cache (or the search index) as two separate operations. Sooner or later one succeeds and the other fails, and the data silently diverges.
The right principle: write only to the system of record, and derive everything else from it — as a single stream of changes. Then the derived sets share one order of updates, they are consistent and rebuildable. Technically this is done with events and the outbox pattern, or via CDC (change data capture — reading the database's log). The same principle underlies event sourcing: the source of truth is the stream of events, and everything else (projections, read models) is derived from it.
Where this applies
The moment a cache, a search index, or a separate mart appears next to the database — you are already managing derived data, whether you called it that or not. The practical frame: explicitly name what is your source of truth and what is derived; give every derived set a rebuild procedure; and never write to a derivative bypassing the source — only derive from a single stream.
Where beginners stumble:
- Dual-writing to the database and the cache/index — two operations without a shared transaction diverge. Write to the source, derive the derivative (events/outbox/CDC).
- No rebuild procedure for a derived set — "reindex from scratch" turns out to be impossible, and losing the index becomes an incident out of nowhere.
- Backing everything up the same way — not separating the irreplaceable source of truth from disposable derivatives wastes effort in the wrong place.
- Treating a read model or a denormalized field as a "second truth" — on a discrepancy you start guessing who's right; the source is always right.
What to read next: the read model in CQRS — derived data shaped for a specific screen; cache invalidation — keeping a derived copy fresh; event sourcing — a stream of events as the source of truth; the outbox pattern — deriving without dual writes. The primary source is Martin Kleppmann, "Designing Data-Intensive Applications," Part III and chapter 10.