Fanout

Fanout is the act of propagating a single logical change to every derived store that depends on it. A write to one source of truth often has to reach several downstream representations: a cache, a materialized view, a denormalized copy, a search index, or the read side of a CQRS system. How and when that propagation happens is the fanout question, and it has two canonical answers.

The same word is used in messaging for the publish-subscribe pattern, which delivers one message to every subscribed consumer. This entry covers the data-systems sense: the trade-off between eager and lazy propagation of writes to derived stores.

Fanout-on-write

Fanout-on-write is the eager strategy. At the moment a write is committed, the system updates every derived store that depends on the changed data. The database index is the familiar example. Each insert, update, or delete rewrites every index that covers the affected row. A materialized view refreshed on commit, a write-through cache, and a denormalized table updated by a trigger all follow the same shape.

The payoff is on the read side. Reads consult a single, ready-made representation and pay no aggregation or join cost, which keeps read latency low and predictable. The cost moves to writes, which must touch every dependent store before they can return. Each extra derived store adds work to the write path and widens the surface that must stay consistent.

The main risks are write throughput and correctness. A write that must update five stores is slower and more failure-prone than one that updates one. If any update fails, the system is left with stale or contradictory copies, so the write path usually needs a transaction, a compensating action, or an acceptance of eventual consistency. Coupling also grows. The write model has to know about every downstream store, which works against a clean single source of truth.

Fanout-on-read

Fanout-on-read is the lazy strategy. The write touches only the primary store, and derived representations are built, or gathered, at read time. A query that fans out issues several sub-queries, one per underlying source, and merges the results before returning them. A scatter-and-gather read across sharded stores is a typical case, as is an application that assembles a view from multiple services on demand.

The payoff is on the write side. Writes stay cheap and local, and the write model never has to know which read shapes exist. New read paths can be added without touching the write path. The cost moves to reads, which pay the aggregation, join, and network cost every time, and which become harder to keep fast as the number of fanned-out sources grows.

The main risks are read latency and load. A single read can balloon into many downstream calls, and a popular read shape can place heavy load on the stores it fans out to. Because nothing is precomputed, there is no ready-made copy to fall back on, and caching the merged result is the usual mitigation.

Choosing between them

Fanout-on-write and fanout-on-read are the two ends of a single read/write trade-off, and most real systems blend the two. The right balance depends on the access patterns. Writes that are rare relative to reads favor eager fanout, because the one-time write cost is amortized over many reads. Read shapes that are varied or unpredictable favor lazy fanout, because precomputing every possible read would be wasted work.

CQRS is the common framing for the hybrid. A write model handles commands, and read models are built from those writes, often asynchronously through change data capture or event sourcing projections. The read models are fanout-on-write outputs, but the fanout is deferred and decoupled from the write transaction, which trades a little freshness for a much simpler write path.

See also