skip to content

What is a 'read model' built by replicating data from other services via events, and why might a Search service maintain its own copy of product data instead of querying the Catalog service directly for every search request?

level: middleimportance: must knowfreq 70%

answer

  1. read model = denormalized query-shaped copy
  2. fed by domain events / CDC
  3. one write model many read models
  4. propagation lag is the cost
  5. reconciliation/backfill catches drift

basics

~20 s

A read model is a service's own local, search or query-optimized copy of data that another service owns, kept updated by listening to that service's change events, so it can answer queries fast and independently instead of calling the owner every time.

solid answer

~40 s

A read model is a denormalized, query-shaped copy of another service's data, built and kept in sync by consuming that service's domain events, or via change-data-capture, and stored in whatever technology best serves the query — a search index, a wide read table, or a cache. Search maintains its own index of product data instead of calling Catalog per search because full-text search, faceting, and ranking need a purpose-built index that Catalog's transactional database isn't shaped for, and because a search request fanning out to Catalog synchronously for every query would create tight availability and latency coupling at high read volume. The cost is that the index is eventually consistent — a catalog update takes some propagation delay to appear in search results — which is normally an acceptable trade for search UX.

go deeper

for a junior

Can explain in plain language that a service can keep its own copy of another service's data to answer queries faster, and that the copy might lag behind slightly.

for a middle

Names domain events or CDC as the mechanism, understands the copy is read-only and eventually consistent, and can give one reason a purpose-built read model beats calling the owner live.

for a senior

Discusses drift detection and reconciliation, backfill strategy for schema changes, and can reason about acceptable staleness windows per use case.

for a principal

Designs the event contract and versioning strategy shared across many downstream read models, weighs the operational cost of many read-model copies against the alternative, and sets org guidance on when a new read model is justified versus over-replication.

## What a read model is A read model is a local copy of data that one service owns, built and maintained by another service purely for reading, shaped specifically for that consumer's query needs rather than mirroring the owner's internal schema. The mechanism: - the owning service, say Catalog, publishes domain events whenever something relevant changes — `ProductCreated`, `ProductPriceChanged`, `ProductRetired` — either explicitly from application code or via change-data-capture tooling, Debezium is a widely used example, that streams row-level changes out of Catalog's own database transaction log; - a consuming service, say Search, subscribes to that stream, transforms each event into whatever shape its own queries need, and writes it into its own storage, commonly a very different kind of storage than Catalog uses, such as an inverted full-text index rather than a relational table; - from that point on, Search answers every query entirely out of its own local index; it never contacts Catalog's database or API at query time. ## Why build one instead of calling the owner The reason a service builds a dedicated read model instead of just calling the owner live for every query is that query needs diverge sharply from write needs. Catalog's own database is shaped and indexed for consistent single-record writes and lookups by ID, which is what its own write path needs. Search needs full-text matching, ranking, and faceting across the entire catalog on every request, at very high read volume — a shape Catalog's transactional store was never built for, and a volume of synchronous calls to Catalog's API that would overload it and couple Search's availability tightly to Catalog's. Building a purpose-shaped, independently-scaled local copy lets each consumer optimize its own read path and remain functional even if the owning service is temporarily degraded. ## What it buys and what it charges The benefit is real: - independently scalable reads; - freedom to pick the best storage engine per query pattern; - and resilience — if Catalog has a bad deploy, Search can keep serving slightly-stale results instead of going down with it. The cost is equally real: - the read model is only eventually consistent, so there's an unavoidable propagation delay between a change in Catalog and its appearance in Search results; - the team now runs and monitors an entirely separate piece of infrastructure, the index, its consumer process, its storage; - and because events can be delivered more than once or arrive out of order, the consumer has to be built to handle that, idempotent writes keyed by event ID or version, rather than assuming a clean, ordered stream. ## Two recurring failure modes In production this pattern shows two recurring failure modes. 1. **The first is silent drift**: the event consumer has a bug, crashes without anyone noticing, or silently drops messages under load, and the read model slowly diverges from the true state of the owning service without producing any obvious error — it just quietly serves wrong answers until a customer notices something is off. The standard defense is a periodic reconciliation job that compares counts, checksums, or samples between the read model and the source, or replays the full event history into a fresh copy and diffs it, to catch this kind of drift before a customer does. 2. **The second failure mode shows up when the owning service adds a new field the read model needs**, but historical events never carried it — this requires an explicit backfill, either a bulk re-sync event from the owner or the consumer calling the owner's API once to fetch the missing field for existing records, since simply consuming new events going forward would leave old records permanently incomplete. ## One write model, many read models A realistic system typically has several read models fanned out from one owning service, each shaped differently: - alongside Search's full-text index of Catalog data, - a Recommendations service might keep a graph-shaped copy of the same underlying products for traversal queries, - and a checkout page might keep a flat, denormalized cache of just price and availability for fast rendering. This 'one write model, many read models' shape is deliberate architecture, not duplication for its own sake — each copy exists because a real, distinct query pattern needed it, and the propagation lag between Catalog's write and any one of these read models catching up is treated as a bounded, monitored freshness SLA rather than an accident.

  • How would you detect that a read model has silently drifted from its source of truth?
    Run a periodic reconciliation job that compares a checksum, count, or sample of records between the read model and the owning service, or its event log, and alerts on mismatches. Some teams also replay the full event history into a freshly built copy on a schedule and diff it against the live read model to catch consumer bugs that caused silent data loss.
  • If the owning service adds a new field that the read model needs, but the field wasn't in past events, how do you backfill it?
    You typically need a one-time backfill: either the owning service publishes a bulk re-sync event or snapshot for existing records, or the consumer calls the owner's API to fetch the missing field for its existing dataset. Going forward, new events include the field so incremental updates stay correct.
  • A user updates their product listing and immediately searches for it, but doesn't see the change. Is this a bug?
    Not necessarily — it's the expected propagation lag of an eventually consistent read model, assuming it resolves within the agreed freshness window, typically seconds. It becomes a bug only if the lag is unbounded, growing, or exceeds the documented SLA, which usually points to a stuck consumer, backlog, or dropped events rather than the pattern itself being wrong.

Like a news wire service: reporters at the owning service file the authoritative story once, and many outlets, the consumers, each republish their own formatted version — a headline ticker, a full article, a radio summary — built for their own audience. Each outlet's copy is accurate soon after the wire update, but not instantaneously, and if an outlet misses a wire update its copy quietly falls behind until someone notices.

saying these in an interview costs you the question

  • Treats a read model as a full replacement source of truth that other services should write to
  • Assumes replication happens instantly with no propagation lag
  • Has no answer for how drift between the read model and source gets detected or fixed
  • Thinks building a read model means duplicating the owner's schema exactly rather than shaping it for the consumer's query
  • Confuses read-model replication with the owning service losing ownership of writes

context