Skip to content

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

Last updated · 200 distilled / 1,178 read