← Stackzilla Blog

Database Migrations Without Breaking the Version Still Running

Published October 23, 2026 · 3 min read · databases, migrations, deployments, backend

Use expand-and-contract migrations to change a database while old and new application versions temporarily coexist.

A database migration does not happen in isolation. During a rolling release, an older server may still handle requests while a newer server expects a different schema. Background workers, dashboards, and scripts can also depend on columns that look unused from the main application. The safest starting question is not whether the new code works with the new schema. It is whether each intermediate state works while the system changes. That is the purpose of an expand-and-contract migration. ## Expand before switching readers Imagine renaming a customer's `name` field to `display_name`. Renaming it immediately can break the old application. Instead, add the new field while leaving the old one available. Deploy code that can tolerate records where the new field has not been populated. Define how writes remain consistent during the transition. Depending on the system, that may mean writing both fields temporarily or using a carefully reviewed database mechanism. Do not assume a one-time backfill captures writes that happen while it runs. Backfill in bounded batches. A single update across a large table can create lock contention, substantial write traffic, and replication lag. Track progress with a stable key so an interrupted job can resume rather than start over. ## Switch only after verifying the data Compare record counts and business invariants before changing readers. A non-null column is not proof that its values are correct. Check representative records, missing values, and transformations involving unusual characters or legacy formats. Deploy readers that prefer the new field once the data is ready. Watch errors and relevant product behavior. Keep compatibility for a defined period rather than removing the old field in the same release. Rollback needs its own plan. If new writes contain information that the old schema cannot represent, restoring an old application version may not be safe. Backups help recover data, but restoring a database can discard legitimate activity that occurred after the backup. ## Contract as a separate release Remove the old field only after old application instances and dependent jobs are gone. Search beyond the main repository when reporting tools or external integrations access the database directly. Before running DDL, understand the database version's locking and rewrite behavior. Setting a short lock timeout can be safer than allowing a schema change to block production traffic indefinitely. Rehearse on realistic data and use the project's established migration workflow. This process takes more steps than a direct rename. Those steps buy compatibility, observable progress, and a controlled recovery path. The benefit is not theoretical elegance; it is avoiding a release where half the servers no longer understand the database. ## Further reading - [PostgreSQL: Modifying tables](https://www.postgresql.org/docs/current/ddl-alter.html) - [Prisma: Expand-and-contract migrations](https://www.prisma.io/dataguide/types/relational/expand-and-contract-pattern)

Read the full article on Stackzilla →