Production Data Systems Handbook / Chapter 12
Schemas, Contracts, Metadata, and Meaning
Treat schemas as shared production contracts by defining meaning, compatibility, lineage, ownership, freshness, privacy, and deprecation.
Preparing audio…
Audio edition
Schemas, Contracts, Metadata, and Meaning
The Schema Passed
A commerce team replaces marketing_opt_in, a boolean, with marketing_consent, a string that can distinguish granted, denied, and unknown. The old name no longer matches the policy language, and the old type cannot represent “we have no answer.” The new field looks like a correction.
The producer tests pass. The warehouse accepts the column. The message broker accepts the event. The API still serializes. Two days later, a support dashboard undercounts customers because older records have no new value. An email job interprets an unrecognized value as permission to proceed. An analyst turns every missing value into a refusal. The bytes are valid; the consumers disagree about the fact.
A production schema is a promise made across time and ownership boundaries. Its types and constraints matter, but so do units, nulls, clocks, privacy, freshness, compatibility, and the decisions consumers are allowed to make. If those promises remain implicit, every consumer invents its own contract—and each invention can continue parsing while the system becomes less trustworthy.
Shape Is Only the Visible Contract
Every shared data interface has a contract surface, whether or not anyone has written it down. A table, event, API response, object-store file, feature table, search document, or materialized view becomes a contract as soon as another component makes a decision from it.
The visible layer describes representation: field names and types, required values, constraints, keys, encodings, and version identifiers. amount: integer belongs here. It tells a parser what can arrive, but not whether the value means cents, reward points, item count, or milliseconds. It says nothing about currency, tax, sign, or whether the amount was authorized, captured, refunded, or merely displayed.
The semantic layer identifies the fact. It records units, allowed and invalid values, identity, null behavior, time semantics, validity, sensitivity, retention, and examples at the boundaries. A field named country is incomplete until it says whether it means billing country, shipping destination, tax residence, market, locale, or an inference from an IP address. The same string can support very different decisions.
The operating layer governs change. It names the producer owner, important consumers, compatibility policy, quality checks, lineage, deprecation window, incident contact, and rollback path. Inside one service, a team may refactor freely. At a shared boundary, it is changing other people’s assumptions.
Meaning Drifts Quietly
The most dangerous schema failures preserve type. A string remains a string and a timestamp remains a timestamp, so validators stay green while the represented fact moves.
Consider status. A young system may define pending, active, and disabled. Fraud later adds held, billing adds past_due, and legal adds restricted. A consumer that treats unknown values as active creates risk; one that assumes the list is closed may drop rows or crash. Adding an enum value can be physically additive and semantically breaking.
Nulls open another drift path. Null can mean unknown, not applicable, redacted, deleted, failed to parse, not yet computed, or intentionally withheld. Empty string, zero, false, and default objects can hide the same ambiguity. A default may keep a rollout moving, but it must not manufacture a fact that the producer never knew.
Time fields require equally exact names. Event time says when something happened in the business process. Commit time says when an authority accepted it. Ingestion time says when a system first saw it; processing time says when a job handled it. Replays, backfills, delayed delivery, and failover make those clocks diverge. A report that substitutes ingestion time for event time can rewrite the apparent past without changing a single timestamp type.
Some facts also need validity windows. A customer’s current plan is not necessarily the plan that applied when an invoice was issued. A current tax category may not describe an old order. A current privacy preference does not by itself establish what a system knew or was permitted to do when an event was created. When those differences affect a decision, the contract needs effective_at, valid_from, valid_through, or an equivalent boundary.
Compatibility Lives in the Consumers
Producers tend to classify changes by the edit they make: add, rename, widen, remove. Compatibility is instead a property of the producer, the consumers, and the retained data considered together.
An optional field is often the safest physical addition. It can still expose sensitive data to broad-access logs or tempt consumers to infer a meaning that has not been promised. A required field makes a stronger claim: every current producer, old message, replay, and honest backfill can supply the fact. The ecosystem has to prove that claim.
A rename is a migration, not a text edit. Usually the producer adds the new field, writes old and new forms during a bounded interval, moves consumers, observes remaining use, and only then removes the old field. A direct rename is safe only when all consumers and retained representations can change together.
Type changes deserve similar suspicion. Integer to decimal, scalar to list, local-time string to timestamp with offset, or JSON blob to structured fields may alter precision, order, parsing, comparison, storage, and meaning. When the fact has changed, a new field or version makes that change visible.
Deletion requires evidence of non-use. Memory and a code search miss dashboards, scheduled jobs, partner exports, generated clients, model features, notebooks, and retained events. Usage telemetry, lineage, contract tests, owner sign-off, and replay tests are stronger. A field that is no longer emitted may still need to remain readable for recovery or audit.
The gate exists to make evolution routine rather than surprising. It should reject an unsafe change, not prevent change itself.
Follow the Consent Change Through Time
Return to marketing_opt_in. Before touching the schema, the owning team has to state the fact it means. Is it the answer a user supplied, an operational decision about whether a message may be sent, or a conclusion derived from policy and other records? Those are not interchangeable. A field named marketing_consent does not settle the question; it may only hide it behind a better noun.
Suppose the old field records a user’s answer and the business now needs to preserve “unknown” rather than collapse it into false. The additive redesign introduces marketing_consent_state with an explicit set of values and consent_recorded_at with declared time semantics. If provenance affects downstream use, the contract also needs a source or method. No field should claim more legal or business meaning than the underlying process establishes.
During the transition, current writes populate both old and new representations where the mapping is honest. An old true backed by a recorded user action may map to granted. An old false maps to denied only if the old process recorded an explicit refusal; otherwise it becomes unknown, as do missing historical records. The old event remains readable, and the new event or field carries a version consumers can identify.
Lineage reveals the support dashboard, email decision path, warehouse models, exports, and other derived copies that receive the fact. Each consumer migrates according to its consequence. A dashboard may tolerate an unknown category. A send-decision path needs an explicit safe behavior and should not improvise one from string truthiness. An export may need both old and new fields during its contractual notice period.
Compatibility tests can prove that old messages still parse and that declared consumer examples still work. Quality checks can compare old and new values where a mapping exists, count unknowns by source and region, and detect producers that stopped dual-writing. None of those checks alone proves that the new field means the right thing. Owners of the business process, privacy boundary, and high-consequence consumers must review that claim.
Only after consumer use of the old field falls to an agreed threshold, retained data remains readable, and the rollback window closes should the producer stop the old write. Removal is the last act of the migration, not the first act of cleanup.
Events and APIs Carry Contracts Beyond a Deploy
Events often outlive the code that emitted them. A user_created event may feed welcome flows, analytics, search, fraud features, permissions, exports, and compliance work for years. Replay turns yesterday’s output into tomorrow’s input. Its contract therefore needs identity, ordering, idempotency, business and processing time, privacy, retention, and replay behavior—not only a list of fields.
It must also say whether the event records a durable business fact or asks a technical projection to refresh. “A user was created” and “rebuild this user’s search document” have different replay and audit consequences even if their payloads resemble each other.
APIs face a different form of time. Mobile applications, partner integrations, devices, cached clients, SDKs, and scheduled scripts do not upgrade with the producer. Producer-side compatibility tests protect the emitted representation; consumer-driven tests can preserve behaviors a known client relies on. Neither test can certify a silent change in business meaning.
Versioning is useful when incompatible meanings must coexist. It also creates maintenance and migration work. Too few versions force synchronized releases; too many leave several contracts alive indefinitely. Introduce one when consumers genuinely need to distinguish meanings, and retire it with the same care used to introduce it.
Metadata Must Operate the Promise
Metadata earns its place when it changes a decision or shortens recovery. Ownership identifies who can approve a change, explain a field, repair bad data, and respond during an incident. The storage platform may run the warehouse, but the domain team still owns what eligible_for_marketing means.
Lineage need not model the whole company. For a critical interface it should answer three practical questions: which upstream change could have produced this value, which consumers may be affected, and which replay or backfill paths need special handling?
Freshness distinguishes expected cadence from observed state. “Daily” is vague; “complete through the previous UTC day” says what a consumer may assume. A stream might promise a bounded lag and expose the present lag beside the data. During a backfill, the freshness indicator should reveal that the usual promise is suspended.
Quality checks should be consequences of the contract: uniqueness for an identity, allowed values for a state, reconciliation for money, timeliness for an operational feed, and distribution or null-rate changes where they expose a broken producer. A dashboard without an owner and a repair action only measures accumulating doubt.
Sensitivity and retention metadata constrain where a field may travel, who may read it, what can be logged or exported, and how long copies may remain. Automatic ingestion is especially hazardous when classification occurs after a sensitive field has already spread into logs, analytical tables, caches, and exports.
A Catalog Is an Index, Not an Owner
A catalog can make contracts discoverable. It cannot create them.
The useful entry is connected to sources of operational truth: schema definitions, migration files, service ownership, access policies, pipeline definitions, observed use, quality results, freshness signals, and deprecation notices. Where possible, those facts should be generated or checked rather than recopied into prose.
A stale description is worse than an absent one when readers trust it. If total_amount now contains authorized value before tax while the catalog still says captured value after tax, the catalog turns documentation into a source of error. Catalog investment should therefore follow consequence. Shared financial facts, privacy-sensitive fields, retained events, operational dashboards, model features, and customer-facing APIs need stronger stewardship than a temporary scratch table.
The failure is not insufficient documentation everywhere. It is allowing consequential data to cross a boundary without an enforceable meaning and an owner.
Write the Contract at the Boundary
A useful data contract is short enough to consult during a change and exact enough to operate during an incident. For each shared interface, record:
- Fact and representation: purpose, fields, types, units, allowed and invalid values, examples, identity rules, and tenant boundary.
- Absence and time: null and default meanings, event and processing clocks, validity windows, timezone, and stale-data behavior.
- Ownership and consumers: producer owner, incident contact, known consumers, high-consequence decisions, and important unknown-consumer surfaces.
- Evolution: compatibility policy, versioning rule, consumer tests, notice period, dual-write or dual-read interval, rollback, and removal evidence.
- Operation: freshness target and indicator, quality checks, lineage, alert owner, backfill and replay behavior, and repair path.
- Harm and lifecycle: sensitivity class, access and masking rules, logging and export limits, deletion, retention reason, and deprecation policy.
The record should end in a decision. Approve the change; require an additive migration or a new version; add a consumer test or quality signal; restrict propagation; lengthen deprecation; or reject the proposal until its meaning is clear. A completed template with no effect on the change is governance theater.
Before approval, ask the questions the consent migration exposed. Can every important field be interpreted without oral history? Do nulls, defaults, clocks, and validity boundaries preserve ignorance instead of inventing facts? Have consumers been classified by consequence, not only enumerated by name? Can old retained data still be read? Will freshness, quality, and remaining old-field use be observable? Is there a named owner when any answer proves false?
Spend Ceremony Where Repair Is Expensive
Contracts, lineage, classification, and deprecation consume engineering time. A private implementation detail, exploratory notebook, or disposable table does not need the process used for a payment event or entitlement source.
The stronger review belongs where data crosses ownership boundaries, survives for replay or audit, affects money or access, carries private information, drives customer behavior, or would be difficult to repair after misuse. The cost should scale with blast radius and reversibility.
That discipline accelerates later work. Teams change a shared interface faster when they can see its owner, consumers, meaning, freshness, sensitivity, compatibility rule, and removal path. Without those facts, every change becomes either reckless or stalled.
Try the Harder Contracts
Write a contract for a user_created event. Define its business meaning, user identity, event time, producer, required and optional facts, null meanings, privacy and retention classes, compatibility rule, freshness expectation, and replay behavior. Then name one consumer whose decision would be unsafe if a physically compatible field changed meaning.
Next, design a backward-compatible migration from full_name to more precise name fields. A two-part parser is not a contract: people may use one name, several family names, patronymics, particles, multiple scripts, or different legal, billing, and display names. Decide which fields users or trusted sources provide, how inferred values are marked, which use cases keep display_name, which consumers migrate first, what quality evidence you need, and what would permit removal of full_name.
If those answers are inspectable, the schema can evolve without asking every consumer to trust a name or type by intuition. The next design decision—whether to keep, split, or replace a data system—can then begin with agreed facts instead of product preferences.
Continue reading
Full table of contents