Production Data Systems Handbook / Chapter 5
Data Models and Query Shapes
Choose data representations by matching facts, commands, query shapes, invariants, ownership, and evolution pressure.
Preparing audio…
Audio edition
Data Models and Query Shapes
One Order, Several Answers
Put one order in front of three teams. Checkout needs to capture payment against the committed total and line items. Support needs to find the order from an email address, SKU, shipment status, or phrase in a note. Finance needs net revenue by channel, currency, discount, refund status, and tax treatment.
Ask for “the order schema” and each team can give a plausible, incompatible answer. Checkout wants a representation that can reject an invalid state transition. Support wants a flattened, searchable document. Finance wants stable facts and dimensions that scan efficiently across months of history. None of these shapes is the order itself.
A production data model is a promise about work: which facts are authoritative, which commands may change them, which queries will be cheap, which invariants the system will enforce, which copies may lag, and which team will own the consequences when the product changes. Tables, documents, keys, events, columns, search documents, graph edges, vectors, and time buckets are ways of keeping that promise.
The design question is therefore not which shape resembles the product vocabulary. It is which representation can preserve truth at the write boundary while making the important access paths practical at an acceptable cost.
Separate the Fact from the Shape
Before choosing a representation, name the facts the system must preserve.
An order was placed. A payment authorization was attempted. A payment was captured. A shipment label was created. A customer changed a delivery address. A support agent added a note. A discount code was applied. A refund was issued. These are not all the same kind of fact.
Some facts describe current state: the order is awaiting fulfillment, the current shipping address is this value, the customer has this active subscription tier. Some facts describe history: payment authorization failed at 10:03, fulfillment retried twice, a fraud review was cleared by a named reviewer. Some facts can be overwritten safely. Others should be appended because audit, replay, dispute resolution, or analytical reconstruction depends on the old value remaining knowable.
The same domain concept therefore takes several modeling roles. An entity such as an order or shipment has identity and a lifecycle. Current state answers what is true now: the payable total, shipping address, or fulfillment status. An event records that something happened at a particular point in the history, such as PaymentCaptured or AddressChanged. An aggregate groups facts that the system usually reads or changes together. A derived view—perhaps a search document or warehouse fact row—is a copy made for a consumer rather than a new owner of truth.
Confusing these roles creates production debt. If only current state is stored when history matters, audit and replay become archaeology. If everything is modeled as events when most work needs current state with synchronous constraints, ordinary commands become projection management. If a derived view is treated as the authority, stale or partial copies can start making decisions they were never designed to make.
The first modeling decision is not “relational or document.” It is “which facts are authoritative, which are derived, and which operations are allowed to change them.”
Query Shape Changes the Model
A query shape is not just a SQL statement or API route. It is the access pattern the system must support: point lookup, ordered range scan, relationship traversal, aggregate scan, full-text search, similarity search, time-window analysis, or stream consumption. It includes selectivity, fan-out, freshness, latency, pagination, sorting, authorization, and concurrency.
Showing one customer’s recent orders is a tenant-scoped range read ordered by time; it needs predictable pagination and authorization. Loading one order is a point lookup plus its line-item aggregate, so locality helps. Preventing a duplicate active shipment is not really a read at all but a command-time invariant that needs an enforcement boundary.
The same order produces two very different scans. Finding a note that mentions “missing adapter” needs full-text search plus filters. Reporting net revenue by channel and month needs a large analytical aggregation with stable definitions. The word order does not tell the storage system which of these operations must be fast.
One representation will rarely serve all five well. A normalized relational model can enforce payment and shipment constraints and support joins, but may not provide good relevance search. A document aggregate can make the order detail page cheap, but can make cross-order invariants and partial updates harder. An event stream can preserve history and feed consumers, but it usually needs projections for user reads. A warehouse table can answer revenue questions, but should not be asked whether an order may ship right now.
This is the central modeling trade-off: write complexity buys read efficiency, read flexibility can make writes and invariants harder, and every derived copy needs an update path.
Where Each Model Puts the Difficulty
Model families are useful vocabulary only after the facts and query shapes are visible. Treat them as places to put complexity, not as maturity levels.
Relational modeling is strongest when relationships, constraints, multi-row changes, joins, and ad hoc operational questions matter. Normalization reduces duplication and gives the database more places to enforce invariants: primary keys, foreign keys, unique constraints, check constraints, and transactions. The cost is that read paths may require joins, migrations require schema discipline, and high-scale partitioning can constrain transaction and query design. A relational model is not automatically slow or automatically correct; it is a bet that shared structure and constraint enforcement are worth the write and schema discipline.
Document modeling is strongest when a coherent aggregate is usually read and written together. It can make a profile page, order summary, configuration object, or support case easy to load without assembling many rows. The cost is duplication, partial-update subtlety, schema drift, document growth, and weaker cross-document invariants unless the design adds explicit mechanisms. A document model is not “schema-free” in production. It has schema; the question is where the schema is enforced, how old shapes are handled, and who repairs drift.
Key-value and wide-column modeling are strongest when access patterns are known in advance and the design can align data with partition keys, sort keys, and bounded query patterns. They can make point lookups and ordered reads very efficient. The cost is rigidity. A new query may require a new table, new key design, denormalized copy, backfill, or application-side fan-out. This model often shifts design effort earlier: you write the queries before you trust the schema.
Event modeling is strongest when the system must preserve change history, feed asynchronous consumers, support replay, or separate the fact that something happened from the projections that serve reads. The hard parts are event identity, idempotency, ordering, versioning, replay safety, privacy deletion, and projection repair. An event log is a strong record of change, but it is not a universal query interface.
Analytical modeling is strongest when the system asks aggregate questions over large sets: revenue, cohorts, inventory movement, product usage, reliability trends, or financial reconciliation. Facts, dimensions, partitions, clustering, and columnar layouts make scans and aggregates practical. The costs are freshness, transformation ownership, metric definition drift, and reconciliation with operational truth.
Search, graph, vector, and time-series models are specialized because their queries are specialized. Search models serve relevance, tokenization, filters, and ranking. Graph models serve relationship traversal and path questions. Vector models serve similarity over embeddings. Time-series models serve high-volume measurements over time windows with retention and downsampling. These models are valuable when the query shape is real. They are risky when introduced as a fashionable second authority.
Follow the Order Through Its Copies
Take one defensible design for the three teams. The operational source of truth uses orders, order_items, payment_attempts, and shipments. Checkout changes them through a transaction that verifies the payable total and the permitted state transition. The same transaction writes an outbox record. This is not the only viable design, but it is concrete enough to reveal where each promise is kept.
An asynchronous publisher reads the outbox and emits a versioned order change. The search projector flattens selected customer fields, line-item summaries, shipment status, and notes into a search document. That document records the source version and indexing time. A separate transformation turns payment, refund, and order facts into a warehouse model with dimensions for date, channel, product, currency, and tax treatment.
Now the support agent searches for “missing adapter.” The result arrives from the search document because that representation understands text retrieval and filters. If the agent opens the order or attempts a refund, the application reads operational truth again. A result that is useful for discovery is not necessarily safe for decision-making.
Finance asks a question that points in the other direction: net revenue for last month after late refunds. The warehouse can answer efficiently, but only if the transformation owns definitions for revenue and tax treatment, incorporates corrections, and reconciles against authoritative payments and refunds. Replaying the transformation should change the derived model, not rewrite operational history.
The order now exists in several places without becoming several truths. The design is credible only while its update paths remain credible. If the transaction commits but the publisher stops, the outbox must preserve work for retry. If the search projection lags, someone must see the lag and support must know what may be stale. If finance changes a tax classification, the team must know whether it corrected an analytical definition or discovered an error in operational facts.
A customer deletion request takes the same route through the system. The source applies the required deletion, redaction, or retention policy. Search, analytics, caches, backups, and replay sources each need an explicit consequence and maximum lag. These are not cleanup details added after modeling. They are tests of whether every copy still remembers its role.
Invariants Belong at the Right Boundary
An invariant is not protected because a field exists in a schema. It is protected because some boundary refuses, serializes, detects, or repairs invalid state.
Relational constraints can be excellent invariant mechanisms when the invariant fits inside the database transaction boundary. Unique usernames, valid foreign keys, nonnegative balances, and “one active subscription per account” may belong there if the model can enforce them without distributed guessing.
Document and key-oriented models can also enforce strong invariants when the invariant is local to one document, one key, or one partition. A conditional write on order_id can prevent two updates from silently overwriting each other. A partition-local transaction can preserve an aggregate. But if the invariant crosses documents, partitions, services, or regions, the model must say which mechanism closes the gap: stronger transaction support, a central authority, idempotency keys, reservation records, sagas, reconciliation jobs, or manual review.
Derived views should rarely enforce primary invariants. A search index should not decide whether a payment can be captured. A warehouse table should not decide whether inventory is available. A vector index should not decide whether a user may see a private document. Derived models can help detect violations, route work, and serve reads, but the design must not confuse read optimization with authority.
When a model makes invariant enforcement awkward, that is evidence. It may still be the right model, but the decision must include the compensating mechanism and the operational cost of running it.
Evolution Is Part of the Model
Data models change because products change. Fields become optional, required, renamed, split, merged, deprecated, or reinterpreted. New query shapes appear. Teams split ownership. Compliance rules introduce retention and deletion requirements. A model that is elegant for the first release can become hostile if it assumes one permanent worldview.
Evolution-friendly modeling favors additive change where possible. It tolerates unknown values. It distinguishes absence from falsehood. It gives events and APIs versioning rules. It keeps derived views rebuildable. It records which consumers depend on which fields. It avoids leaking storage-specific details into public contracts unless the team intends to support those details.
Ownership is what turns this from style advice into production practice. If the order service owns operational truth and a data platform team owns warehouse transforms, their contract should cover schema change, freshness, failed transformations, backfills, deprecation, and reconciliation. If support owns search relevance, it still needs an agreement with the source system about which facts may be indexed, how quickly they update, and how corrections flow.
A model without an owner is not flexible. It is abandoned.
Decision Artifact: Model-to-Query Map
Use a model-to-query map before choosing or changing a representation. Begin each row with a real command, query, import, report, or consumer. Describe its shape precisely—point lookup, ordered range, aggregate, traversal, text search, similarity search, time window, stream consumption, or bulk export—and add the required latency, freshness, consistency, ordering, authorization, retention, and pagination behavior.
Then name the model allowed to decide truth, any derived model, and the complete update path between them. Finish with the invariant or accepted debt and its owner. The map earns a table because the rows must be compared: the reader should be able to see where one fact crosses representations and whether the same copy has quietly acquired conflicting jobs.
| Command or query | Shape and required behavior | Authority and derived view | Update path | Invariant, debt, and owner |
|---|---|---|---|---|
| Capture payment | Constrained state transition; current truth required | Operational order and payment model; no derived copy decides | Transaction at the command boundary | Captured amount must match the committed payable total; order team owns enforcement and repair. |
| Load order detail | Point lookup plus aggregate read; bounded stale display may be acceptable | Operational model, optionally fronted by a cache or read model | Read-through cache or projection with source version | Payment and shipment actions recheck truth; order team owns invalidation and fallback. |
| Search support notes | Text search with filters; lag must be visible | Operational model is authoritative; search document serves discovery | Outbox projection with retry and full reindex path | Search cannot authorize restricted data or state changes; support-search owner monitors lag and failures. |
| Report daily revenue | Large aggregation by dimensions; late corrections expected | Payments, refunds, and order facts are authoritative; warehouse fact table serves analysis | Batch or streaming transform with reconciliation and backfill | Finance and data owners control definitions, correction policy, and unresolved differences. |
| Delete or redact customer data | Multi-copy lifecycle command with a policy deadline | Policy acts on authoritative facts; every derived copy is in scope | Source change followed by tracked propagation, verification, and backup handling | No copy may silently outlive policy; data owner records exceptions, maximum lag, and completion evidence. |
The map exposes whether the design is buying read efficiency with acceptable write complexity. It also prevents a common shortcut: optimizing the first visible read by letting every convenient copy look authoritative.
Failure Modes
The first failure is modeling for the UI alone. A nested screen proves that one read path wants a nested response. It does not prove that the source of truth should be one nested document, or that all nested facts share the same lifecycle, owner, and invariant boundary.
The second failure is normalizing or denormalizing by ideology. Normalization can protect invariants and reduce drift. Denormalization can make reads fast and local. Either can be wrong when it ignores query shape, write fan-out, ownership, or repair.
The third failure is hiding derived-view operations. A search document, warehouse table, feature store, cache, graph projection, or time-series rollup must be refreshed, monitored, rebuilt, secured, and eventually changed. If the team cannot explain the update and rebuild path, the model is incomplete.
The fourth failure is treating “schema flexibility” as the absence of schema work. Flexible models still need compatibility rules, validation, backfills, readers that tolerate old shapes, and cleanup for fields that no longer mean what their names imply.
The fifth failure is choosing a specialized representation before proving the specialized query exists. Graph, vector, search, time-series, and analytical models can be exactly right. They can also become expensive side systems that duplicate truth without enough value to justify their operational surface.
Review the Map Under Pressure
A convincing map survives a change in the workload. Add a new sort order, a tenfold increase in one tenant’s history, a stricter freshness target, a regional boundary, or a deletion deadline. Ask which read becomes a scan, which write gains fan-out, which invariant crosses a boundary, and which rebuild no longer fits its window.
Then change the organization. Move warehouse ownership to a platform team or split checkout from fulfillment. If the model depended on one team coordinating a transaction, interpreting an event, or repairing a projection, the new boundary must appear in the update path and operating agreement. Ownership is part of the model because ownership determines whether drift will be noticed and repaired.
Finally, remove a representation. If nobody can explain how to rebuild the search index, migrate an event consumer, retire a denormalized table, or prove that a cache contains no unique facts, the copy has more authority than the diagram admits.
Data modeling is the decision about where truth lives, how work reaches it, and which copies may exist without forgetting that they are copies.
Practice: Change the Model, Name the Debt
Pick one feature with both commands and reads: order checkout, subscription billing, project management, invoice approval, booking, notification delivery, or access review.
Model it twice. First, use a relational model or another constraint-friendly source-of-truth model. Second, use a document, key-oriented, or event-centered model. For each version, write the model-to-query map for at least three commands and three reads.
Then add one derived view that makes an important read faster: a search document, analytical fact table, cache, graph projection, vector index, or time-series rollup. Name the source of truth, update path, acceptable staleness, rebuild procedure, invariant that must not move into the derived view, and owner who will repair it when it drifts.
The exercise is complete only when the trade-off is visible in both directions: what the model makes cheap, what it makes expensive, and what the team must operate because of that choice. Once the model has made those access paths important, the next question is physical: where will the storage engine make the system pay to serve them?
Continue reading
Full table of contents