Skip to content

Versioned Schema Change

Schema-evolution procedure — instantiates Access-Optimized Redundant Representation

Evolves the schema of a redundant representation without breaking its readers, by adding the new shape alongside the old, migrating, then retiring the old once nothing depends on it.

Versioned Schema Change is the procedure for changing the shape of a redundant copy — and propagating that change to everything derived from it — without a flag-day outage. Its defining move is that old and new schemas coexist: rather than mutating a live schema in place and breaking every reader at once, you add the new shape beside the old, let producers and consumers migrate on their own clocks under explicit version markers, and remove the old shape only once nothing still speaks it. It is about schema evolution, not data synchronization: the concern is that a shape change ripples safely across the source and all its copies while the system stays up and readers stay whole.

Example

An order-events read model, long assumed to be US-dollar, must gain an explicit currency field, and several independent services consume it. A blunt ALTER plus rewrite would break every consumer the moment it shipped. Instead the change runs in three moves. Expand: add currency as optional; producers begin writing it; consumers are updated to tolerate its absence. Migrate: backfill historical rows to USD so the field is populated everywhere. Contract: once every consumer advertises that it requires the field and history is filled, make currency mandatory and drop the "assume USD" logic. Each stage is gated on a version token that consumers report, and every step applied to the read model is logged, so if migration misbehaves a rollback knows the exact state to return to.

How it works

  • Never mutate in place. A live schema is not edited under traffic; the new shape is introduced alongside the old.
  • Expand, migrate, contract. Add the new shape and dual-populate; backfill and shift readers over; remove the old shape only when nothing depends on it.
  • Gate on version, log each step. Version tokens let old and new readers coexist and negotiate; every schema step applied to each projection is recorded for state and rollback.

Tuning parameters

  • Compatibility mode — backward, forward, or full compatibility during the transition; stricter modes widen who can migrate lazily.
  • Rollout gating — how version negotiation works (a token consumers advertise vs. a date everyone must hit); usage-gating is safer, date-gating is simpler.
  • Migration window — how long old and new shapes coexist; longer is safer but accretes complexity.
  • Contract trigger — what finally retires the old shape: telemetry proving nobody reads it, or a hard cutoff date.

When it helps, and when it misleads

Its strength is evolving a schema under live traffic with no coordinated outage — every dependent copy and consumer moves when ready. It misleads when the contract step is skipped: old shapes then linger indefinitely, dual-write logic never comes out, and the schema silts up with permanent "temporary" compatibility.[1] It also misleads when a change assumed compatible actually isn't, so a "minor version" quietly loses data. The classic misuse is shipping a breaking change in place under deadline pressure and relabelling it a version bump. The discipline is version tokens that genuinely gate reads, and never contracting until telemetry proves the old shape has no readers left.

How it implements the components

Versioned Schema Change realizes the schema-evolution side of the archetype — carrying a shape change safely across the source and its copies:

  • schema_change_propagation_plan — the ordered expand→migrate→contract plan that moves a shape change across the source and every derived copy without a flag day.
  • version_token — old and new schema versions coexist, each tagged, so producers and consumers negotiate which shape they speak during the transition.
  • projection_change_record — each schema step applied to each projection is logged, so the current state is knowable and a rollback is exact.

It does not rebuild historical rows into the new shape (that's Backfill and Rebuild Job), gate an individual read on the reader's version (that's Read-Model Version Gate), or run steady-state data synchronization (Change-Data-Capture Propagation).

Notes

The hard part is not the schema statement — it is knowing when every consumer has moved off the old shape so you can safely contract. Instrument reads by version and let that telemetry, not a hopeful calendar date, decide when the old shape is dead. Contracting on a guess is how a "compatible" migration turns into an outage.

References

[1] Expand-and-contract (also called parallel change) — the migration pattern of adding the new form, running old and new in parallel while callers migrate, then removing the old form once unused. It is the standard discipline for changing an interface or schema under live traffic; skipping the contract phase is what leaves systems permanently carrying both shapes.