# A database migration has to support two application versions

During a rolling release, old and new code share the database. Design the transition around that overlap before removing or reinterpreting stored data.

By Cobnex editorial. Published 2026-09-10. Updated 2026-09-11.

## The schema changes before every process does

Imagine renaming a customer reference column. The migration succeeds, but an older worker still queries the previous name. The web application looks healthy while background imports begin failing.

A deployment is not one instant shared by every process. Web instances, scheduled jobs, queue workers and rollback versions can run different code against the same database. The migration must account for that period explicitly.

Start by listing readers and writers. Include reporting connections and operational scripts. An apparently unused column may still support a monthly export that never appears in a short traffic sample.

## Separate expansion from removal

Introduce the new representation while the old one remains usable. Update application behaviour, move historical data and verify the new path. Remove the old representation only after its consumers and recovery dependencies have been retired.

This sequence is often called expand and contract. Its value comes from separating incompatible changes, not from following a fixed number of deployments. A small additive field and a new ownership model require different evidence.

### Schema transition with an overlap period

The database supports existing readers while the new path is introduced and verified. Removal follows demonstrated independence from the old representation.

1. **Expand**: Add a compatible destination for new data
2. **Move behaviour**: Update writers and backfill with concurrency controls
3. **Verify**: Compare results and exercise rollback
4. **Contract**: Remove old dependencies after the recovery window

## Decide which representation is authoritative

During a transition, two columns or tables may describe the same fact. Specify which one determines the correct value and how changes reach the other.

Dual writes inside one database transaction can keep related changes together when the data model permits. Separate writes to different systems need a recovery protocol. A failed second write cannot be treated as if both succeeded.

Backfills also race with live edits. A worker that reads an old value and later writes its conversion may overwrite a newer update. Use conditional updates, revision checks or another explicit coordination method. Merely processing rows in small batches does not solve this correctness problem.

## Treat database work as production load

A schema command can wait for locks, scan data or rewrite storage depending on the operation and database version. Review the actual execution behaviour rather than assuming every additive change is cheap.

Set appropriate waiting limits and observe application latency during rehearsal. Test with representative data size and long-running transactions. An empty development database cannot reveal the same contention.

Plan storage and replication effects as well as command duration. A migration may finish on the writer while replicas continue catching up.

## Preserve a useful recovery path

Rolling back application code is safe only if the previous version can still read and write the resulting data. Dropping a column or introducing values it cannot interpret changes that promise.

Name the last reversible checkpoint and the recovery action after it. A backup is evidence of stored history, not a complete plan for preserving writes made since that backup. Demonstrate how the application resumes with correct data before calling the migration reversible.

## Sources

- [PostgreSQL: ALTER TABLE](https://www.postgresql.org/docs/17/sql-altertable.html)
- [PostgreSQL: explicit locking](https://www.postgresql.org/docs/17/explicit-locking.html)
