advanced25 min studyLesson 386 in Engineering

Expand-contract database migrations

Separate additive schema preparation, compatible application rollout, verified backfill, and destructive cleanup so old and new versions can coexist deliberately.

On this page

What you’ll understand

  • Build a read/write compatibility matrix across application and schema versions.
  • Plan a resumable backfill that does not overwrite newer writes.
  • Identify evidence required before removing the old representation.

An application rollout and its database migration rarely become visible everywhere at one instant. Expand-contract treats the mixed-version interval as a normal operating state: add a representation, make code compatible, move and verify data, switch ownership, and only then remove the old contract. The pattern is a sequencing tool, not a guarantee that every additive change is cheap or every dual write is correct.

Expand the contract before requiring it

Begin by listing what old and new binaries read and write. An additive nullable column can let old code continue using the original field while new code learns the expanded schema. A new NOT NULL requirement, changed default, renamed column, or altered value meaning can still break old behavior or require expensive work. PostgreSQL DDL has documented locking and rewrite rules, so assess the exact statement rather than categorizing every ALTER TABLE as either free or fully blocking. Set bounded operational expectations and observe the deployed database version.

Compatibility is directional. New code may read old and new rows while old code understands only the old representation. During overlap, a writer may need to maintain both forms within one transaction if they belong to the same database, or use a deliberately reconciled protocol if effects span systems. Two independent writes can fail halfway and disagree. A fallback reader must distinguish genuinely absent migrated data from a valid NULL or empty value, and define which representation wins when both exist. Otherwise the migration silently changes business meaning.

Cij={1,application i can safely read and write state j0,otherwiseC_{ij}=\begin{cases}1,&\text{application }i\text{ can safely read and write state }j\\0,&\text{otherwise}\end{cases}
This dimensionless compatibility matrix is a design artifact supported by tests and contract review, not a probability. Read and write compatibility may require separate matrices when one direction succeeds and the other fails. Every actually reachable mixed-version pair needs an explicit decision.↔ Scroll wide equations horizontally.

Backfill with a resumable concurrency contract

Choose bounded batches, an observable progress marker, and a retry-safe rule for each row. A cursor over stable keys can record where work has reached, but rows may change while the backfill runs. Avoid replacing a newer application write with an older snapshot value. Conditional updates, row versions, appropriate locking, or an authoritative transformation inside the database can protect the intended ordering. Which mechanism is correct depends on the read/write protocol. A checkpoint merely saying batch 40 finished does not establish that concurrent updates preserved both representations.

Measure write amplification, lock waits, replication lag, storage growth, and user-visible latency while backfilling. Throttle or pause work according to a declared operational budget. A count of processed rows is not the same as a count of correctly migrated current rows; reconciliation should compare the intended invariant, including rows created or modified after the scan began. Validate representative edge cases and use aggregate or sampled checks only with their stated coverage. An irreversible transformation can require retaining original information through the rollback horizon.

Tideal=Nr,Dextra=NwT_{\rm ideal}=\frac{N}{r},\qquad D_{\rm extra}=Nw
N counts existing rows, r is a constant sustained completed-backfill rate in rows per second, and w is extra logical bytes retained per row. T_ideal is seconds and D_extra is bytes. Pauses, retries, concurrent changes, indexes, WAL, storage overhead, and replication are excluded.↔ Scroll wide equations horizontally.

Contract only after old dependencies are gone

Switch readers and writers in an order supported by the compatibility matrix, observe that the new representation is authoritative and complete, and wait until old binaries, jobs, and rollback candidates no longer require the old field. Only then consider destructive cleanup. Deploying a new frontend does not prove that a forgotten batch job stopped writing the old representation. Record the cutover revision and reconciliation evidence. Keep backup and recovery procedures aligned with the schema transition rather than treating a column drop as routine garbage collection with no state consequences.

The queue lab can illustrate a simplified migration-work backlog. Reset, set both λ=1 and μ=2, predict its initial four tasks drain at t=4, then Play or Step. Separately Reset, set both λ=0 and μ=2, predict t=2, then Play or Step. New repair tasks arrive only in the first model; real dual-write and backfill rules determine whether such tasks exist. The fluid instrument does not execute SQL, model locks, or validate row versions. Its M/M/1 readout has separate assumptions and supplies no migration-completeness proof.

Budget a bounded additive backfill

A table has five million existing rows. A stipulated backfill completes 10,000 rows per second without pauses or retries, and the expanded representation retains 24 additional logical bytes per row. Ignore indexes, WAL, and physical overhead.

  1. Divide 5,000,000 rows by 10,000 rows/s to obtain an ideal 500-second backfill duration, or eight minutes twenty seconds.

  2. Additional logical payload is 5,000,000×24=120,000,000 bytes, approximately 114.44 MiB. The actual storage and replication costs can be substantially larger.

  3. If operations deliberately pause the job for a cumulative 180 seconds, elapsed duration is at least 500+180=680 seconds under the same sustained completion rate. Current-row correctness still requires the separate concurrency and reconciliation rules.

The result

Throughput and payload estimates support planning but do not certify that mixed-version reads and writes remain compatible or that every current row is valid.

Explore the model

Interactive lab

Interactive lab · queue

Backfill and repair arrivals share capacity

Reset, set both λ=1 and μ=2, predict the four-task initial backlog drains at t=4, then Play or Step. Separately Reset, set both λ=0 and μ=2, predict t=2, then Play or Step. This equal-task fluid analogy executes no SQL and has no row-version, locking, dual-write, or completeness checks.

Backfill and repair arrivals share capacityFluid backlog starts at 4 and follows max(0, 4 + (λ − μ)t). The separate M/M/1 residence-time formula assumes Poisson arrivals, exponential service and λ < μ. Current readings: Fluid backlog: 4; Net rate · λ − μ: −1 /s; M/M/1 mean residence: 1 s. All parameters are adjustable with the labeled controls below.02.557.51001.534.56Elapsed time · sFluid backlogλ < μ
Fluid backlogCurrent time
Fluid backlog
4
Net rate · λ − μ
−1 /s
M/M/1 mean residence
1 s
t = 0 s

Fluid backlog starts at 4 and follows max(0, 4 + (λ − μ)t). The separate M/M/1 residence-time formula assumes Poisson arrivals, exponential service and λ < μ. Parameter changes pause playback and recompute the model at the current elapsed time. Reset restores both parameters and returns time to zero. Playback starts paused. Adjust either parameter, then choose Play or Step.

Look forA shrinking work queue is useful evidence of progress, while migration correctness requires a separately verified data invariant.

Check your understanding

A backfill reads an old row value, then the application writes a newer value before the backfill stores its derived field. What property is needed?

Try it yourself

Version A reads and writes field old. Version B can read both but writes only new. Is adding field new enough to permit both versions to run indefinitely? Explain one necessary additional contract.

Take a moment to make a prediction or work it out on paper. Open a hint when you need one.

Give me a hint

Follow a write from each version to a later read by the other.

Compare with the solution

No. A write by A may leave new stale, while a write by B may leave old stale for A. The overlap needs an authoritative representation and a compatible synchronization or dual-write protocol with failure handling, plus reader precedence rules and reconciliation. Schema presence alone does not establish cross-version data compatibility.

Take it with you

  • Separate schema expansion, compatible code, backfill, cutover, and contraction.
  • Protect current writes against stale backfill updates.
  • Destructive cleanup requires evidence that old readers, writers, and recovery paths no longer depend on the old form.

Read further

Original sources and deeper explanations to follow at your own pace.

Optional progress, saved in this browser when available. Revisit any lesson at any time.

Lesson not marked complete.