The short version

Expand contract migration introduces a new database structure alongside the old one, moves data and application behavior, then removes the old structure after its users are retired. For AI-generated changes, review compatibility at every stage. Faster code generation does not establish that existing workers, reports and rollback versions will still function.

The idea of this article, drafted in 18 seconds. Tap the speaker for sound.

The generated diff is only one part of the change

A coding agent can propose a cleaner data model and update the application in one patch. Production may still contain an older web instance, a delayed job or a reporting integration that expects the previous schema. Review every reader and writer before treating the new model as ready.

Illustrative example: a support system replaces an is_closed flag with a richer lifecycle_state. A direct removal of the flag could break a worker still using it. The migration also needs an explicit business mapping: false does not reveal whether an existing ticket is new, assigned or waiting. An agent should not invent that missing history.

The expand contract migration pattern

Prisma documents the principle of adding a replacement field and moving data before removing the old field once it is no longer read. Apply that principle through separately reviewable stages, with application compatibility checks between them.

For the illustrative ticket change, first add the new representation without breaking existing code. Define how reads and writes remain consistent while both representations exist. If older writers can still change is_closed, a one-time copy is insufficient: choose and test a synchronization strategy or retire those writers before switching authority.

Backfill historical records using an agreed mapping, preserving uncertainty where history cannot be reconstructed. Verify the mapping against business examples and reconcile new writes during the transition. Remove the old representation only after its consumers have been identified and retired, including jobs and reports.

Database migration testing includes mixed versions

Test against representative synthetic or appropriately sanitized data in an isolated environment. Include old code against the expanded schema, new code before and after backfill, and an older worker processing a delayed task. Check business results and data consistency, not only whether the migration command exits successfully.

Schema compatibility and operational impact are different questions. PostgreSQL 18 documents that ALTER TABLE lock requirements vary by operation and default to ACCESS EXCLUSIVE unless stated otherwise. Review the exact statement for the deployed database version and test its behavior under representative load. An additive change is not automatically free of blocking.

  • Inventory application versions, jobs, reports and integrations that access the changed fields.
  • Define acceptance queries for mapped, unmapped and newly updated records.
  • Test an interrupted backfill and its safe continuation without overwriting newer values.
  • Record lock behavior, execution time and the conditions that require pausing the migration.

Define rollback before removing compatibility

Rolling back application code does not restore a deleted field or recover its lost values. Record which application versions are compatible with each schema stage. While the old path is retained, test that it remains usable with current data; after contraction, the earlier version may no longer be a viable rollback target.

Keep a tested recovery plan and a named decision maker for a failed migration. A backup is useful only when its restoration procedure and the handling of changes made since that backup are understood. Do not advertise zero downtime from the pattern alone: table size, write load, migration details and recovery requirements determine the actual plan.

Ask a coding agent for a staged proposal, a dependency inventory and test cases. A responsible engineer must verify those outputs against the running system and the business meaning of the data. Treat the contraction stage as an explicit release decision rather than automatic cleanup.

Keep rapid development connected to continuity

An experienced developer can use AI to prepare migration code and tests quickly, then focus review on compatibility and data semantics. Bring in database or domain expertise when the migration exceeds the team's experience. Measure the rehearsed migration before choosing its production execution window.

UVISION provides dedicated developers and managed projects with at least one full-time developer per project. In critical legacy systems, that ongoing responsibility includes understanding existing consumers, preserving business rules and maintaining the transition through completion. The first useful deliverable is a migration plan whose intermediate states can be explained and tested.

Common questions

What is database migration?

A database migration changes a database's structure or moves its data into a new representation. In an operating business system, the change must account for applications, background jobs and integrations that still use the old structure. An expand contract approach stages the transition, but compatibility, data meaning and operational impact still need explicit review and testing.

How do you handle database migrations?

Identify every reader and writer, define the intended business mapping and plan compatible intermediate stages. Add the new representation, manage consistency during the transition, verify migrated data and retire old consumers before removing their fields. Record which application versions remain usable at each stage, and establish a tested recovery plan before executing the production change.

How do you test a database migration?

Use an isolated environment with representative data and test both old and new application behavior across the planned schema stages. Check delayed jobs, concurrent updates, interrupted backfills and the accuracy of transformed records. Inspect lock behavior and runtime under representative load. A successful command alone does not show that business behavior or rollback compatibility has been preserved.

Further reading

Want to explore what this means for your systems?

Explore the relevant service