PATTERN Cited by 1 source
Async-projected read model¶
Async-projected read model is the operational shape of CQRS: a write-optimized source of truth (normalized relational DB, event log, document store) is the command side, and a separate, derived read-optimized store is built from it asynchronously. The read store may be a graph, a denormalized row set, a search index, an aggregate table, or a cache — whatever the query path actually needs. Reactive rebuilds keep it current.
Canonical shape¶
Writes Async projection Reads
│ │ │
▼ ▼ ▼
Source of truth ─► transformer / rebuilder ─► Read model(s)
(ops-friendly) (triggered by writes ▲
or scheduled) │
Queries
The read model can be:
- A graph — Canva's routing graph in Redis, rebuilt reactively on any relevant relational write.
- A search index — Elasticsearch projection of primary-store rows.
- An aggregate — warehouse table populated by a DBT model (see patterns/end-to-end-recompute).
- A serving-tier row — the row an app API returns; see patterns/warehouse-unload-bridge for the OLAP→OLTP variant.
When to use it¶
You reach for this when:
- The write shape and the read shape genuinely want to be different stores — relational for small edits vs. graph for traversal, transactional for correctness vs. columnar for scan.
- The read workload is high-volume and the write workload is low-volume (Canva: supplier data rarely changes; routing queries run on every checkout).
- The read model is fully derivable from the source of truth (rebuilding after loss is tolerable, because the source of truth is enough).
- You want to A/B-test or upgrade the read representation without touching writers.
Implementation notes¶
- Reactive trigger vs. schedule. Reactive (change data capture / event-driven projection) keeps the read model close to real-time; scheduled rebuilds are simpler but introduce explicit staleness. Canva picks reactive rebuild so supplier-config changes propagate into routing results quickly.
- Whole-artifact rebuild vs. incremental patch. Small read models can be rebuilt from scratch on every trigger (simpler, no consistency bugs). Large ones need incremental update, which introduces "divergence from source" as a failure mode. Canva's per-region graph is small enough to publish as a whole artifact.
- Source-of-truth invariant. The read model is never authored directly; if it's lost or corrupted, it's rebuilt. This is the disaster-recovery story and also the justification for not worrying about cache invalidation.
- Shard the read model by the query dimension. Canva shards by destination region, matching the actual query shape (see concepts/geographic-sharding). A single giant read model is often the wrong answer.
Consistency model¶
Eventual consistency by default — there is a lag from write to read-model update. Whether this is acceptable is the first question to answer. For Canva's routing, supplier-config staleness measured in seconds is fine; for a billing-relevant read model, probably not.
Seen in¶
- sources/2024-12-10-canva-routing-print-orders — relational operations DB → async reactive rebuild → per-region graph in Redis; the source of truth in relational makes graph loss tolerable (just rebuild).
Related¶
- concepts/cqrs — the general principle
- patterns/warehouse-unload-bridge — the OLAP→OLTP variant
- patterns/end-to-end-recompute — batch-style variant where the read model is rebuilt end-to-end per run
- systems/canva-print-routing