- A schema may serve old and new application versions simultaneously.
- Backfill completion and compatibility are separate milestones.
- Remove old structures only after verifying that their consumers are gone.
The database outlives one deployment step
A developer renames a column and updates the application in the same change. In a local environment, the transition appears atomic because there is one process and one database. A rolling deployment is different. Old instances, new instances, background jobs, and external clients may overlap. The schema has to serve the consumers that actually exist during that period. A migration plan therefore needs to describe a sequence of compatible states, not merely the final schema the developer wants.
Prisma documents the expand-and-contract pattern for changing data structures through staged transitions. PostgreSQL documents the behavior and locking implications of supported schema operations. These references provide tools and examples, while the application’s compatibility requirements determine the actual plan. A pattern name is not a guarantee of zero interruption. The team needs to evaluate data volume, operation behavior, and every relevant consumer before deciding that a transition is safe for the deployed service.
Expand without making old code wrong
Consider a hypothetical customer table that needs a new status representation. An expansion phase might add the new structure while leaving the existing one usable. The application can then be updated to understand the transition. The exact design depends on how writes occur and which source is authoritative during the overlap. Avoid a vague instruction to write both without defining what happens when one write succeeds and another fails. Compatibility requires a coherent data model, not just extra columns.
Inventory all consumers. A background export, an administrative script, a reporting query, or a mobile client may rely on the old structure long after the main web deployment changes. Name an owner for each dependency and decide how its migration will be verified. An untracked consumer can turn a carefully staged release into a delayed incident when the old column is finally removed. The expansion phase buys time only if the team uses that time to identify and transition the real readers and writers.
Backfills are production workloads
Populating a new structure from existing records can consume CPU, storage bandwidth, locks, and replication capacity. A migration tested on a small development database may behave very differently on production volume. Plan how work is divided, how progress is recorded, and how it can pause or resume. The backfill should be repeatable or otherwise recoverable through a documented mechanism. A single long operation that must restart from the beginning can create unnecessary risk and operational pressure.
Preserve correctness while new writes continue. The plan should explain how records changed during the backfill reach the new representation. Depending on the application, this may involve a controlled write path, version checks, or another supported synchronization design. Test races deliberately in a nonproduction setting with representative behavior. Counting rows at the end is not enough if some rows contain an older value. The validation needs to establish that the new representation reflects the intended state, not merely that it is nonempty.
Constraints should follow evidence
A new constraint can express a useful invariant, but existing data and overlapping application versions may not yet satisfy it. Decide when and how the constraint is introduced using the database’s documented mechanisms. Review locking and validation behavior for the selected version. A constraint that is cheap on an empty table can be operationally significant on a large active one. The migration record should state what evidence establishes that the data is ready and what the team will do if validation finds exceptions.
Do not silently discard exceptions to make a migration pass. A record that violates the new model may reveal a legitimate business case the design overlooked. Classify the exception and resolve it with the data owner. If a temporary compatibility rule is needed, give it a clear lifetime. This preserves the migration’s value as a deliberate change in the data contract. Otherwise, the new schema can begin its life with unexplained special cases that future maintainers cannot distinguish from accidental corruption.
Rollback depends on the state already written
An application rollback is easy to describe when the old code can still read all data. It becomes more difficult after new code writes values the old version cannot represent. Define that boundary in the release plan. A staged rollout can retain compatibility for a period, but it cannot make every future transformation reversible automatically. The team should know which step changes rollback from restoring an earlier application artifact to performing a separate data recovery or forward correction.
Test the rollback path with data created by the new version. A test that rolls back before any new behavior occurs proves a much narrower case. Include background work and external effects where relevant. If rollback would lose or misinterpret data, make that limitation explicit before deployment. A planned forward fix can be an appropriate strategy, but it should not be discovered during an incident after someone assumes an old image is a complete recovery method.
Operational checks should reveal old usage before contraction. Query logs, application telemetry, or controlled deprecation signals can help identify remaining consumers, subject to privacy and performance constraints. Keep the evidence specific enough to support removal. A calendar date alone does not prove that every consumer migrated. The contract phase is justified by observed compatibility and ownership, not merely by the passage of time since the new column appeared.
Contract only after the transition is real
The final removal should be its own reviewable change with prerequisites, validation, and a recovery plan. Confirm that old application versions cannot return unexpectedly through autoscaling or an outdated deployment reference. Update documentation and operational tools so they no longer encourage use of the removed structure. Clean up temporary synchronization logic when its purpose ends. Leaving every transitional component in place indefinitely creates another kind of complexity, even if the immediate migration avoided an outage.
Expand and contract is useful because it acknowledges that a live system changes through time. Its success depends on the consumers, data movement, constraints, and rollback boundary being explicit. Treat the backfill as a workload, validate meaning, and remove old structures only after the evidence supports it. The strongest migration plan explains each intermediate state well enough that operators can tell where the system is and which changes are still safe to make.
Sources & further reading
Primary references inform the reporting. Our explanations, scenarios, and conclusions are editorial analysis; source statements are not independent test results.
- Prisma: Expand and contract data migrationwww.prisma.io
- PostgreSQL: ALTER TABLEwww.postgresql.org
Source publication or reference date: 2026-11-04.
See an error or an important update? Contact the editors.



