Skip to content
← Insights

Database Schema Changes: How to Deploy Without Service Downtime

Learn how to deploy schema changes while application versions coexist, using validation and gradual removal to reduce disruption and risk.

Diagram of a schema change deployed in stages, with application versions coexisting before the old schema is removed.

Changing a table may seem like an internal adjustment, but a production application rarely has just one database consumer. Different versions of a service may run at the same time during a deployment; reporting queries, integrations, or scheduled tasks may also depend on the current schema. If a change breaks compatibility with any of these consumers, the result can be a visible error or an outage.

The safest approach is not to change the schema and the code at the same time, but to divide the work into compatible steps. This allows each version to keep operating while the next one is deployed and its behavior is checked. This approach does not eliminate all risk: it requires understanding dependencies, observing the system, and deciding in advance how to respond if something goes wrong.

Why a small change can break a service

Why a small change can break a service

The schema is a contract between the database and all the software that queries it. An older application may expect a column that a migration removes; a newer one may assume that a column already exists when it has not yet been created. Both situations are common when application and database deployments do not happen at exactly the same time.

How the operation runs matters, too. Depending on the database engine and the change, an operation may block writes, take a long time to complete, or compete for resources with normal workloads. Do not assume an operation will be instantaneous or have the same impact in every environment. Check its behavior for your engine and version, the size and usage of the tables, and the specific migration mechanism.

Before planning, identify who reads and writes the data: application versions, connected services, analytics queries, batch processes, scripts, and manual operations. Also review less visible dependencies, such as stored procedures, views, and constraints. If you cannot confirm which components use a field, do not remove it yet.

Apply the expand-and-contract pattern

The expand-and-contract pattern divides an incompatible change into phases. First, the schema is expanded additively; then code that can coexist with the previous version is deployed; finally, the old element is removed once no consumers need it. Compatibility between stages is the key condition: during the transition, every active version must be able to work with the available schema.

  1. Expand: Add the new column, table, or structure without removing or changing the old one yet. Avoid introducing a constraint immediately if existing data or the older application cannot satisfy it.
  2. Prepare the application: Release a version that can handle the old and new schemas as needed. Deploy gradually and confirm that old instances are no longer active before assuming there are no more old reads or writes.
  3. Transition: Switch reads or writes to the new representation. If both columns need to be maintained, define the source of truth and how the columns will stay synchronized during the transition.
  4. Contract: After verifying that the code and processes no longer use the old element, remove it in a later change. This phase should be independent of the deployment that started the transition.

Separating the phases prevents a gradually deployed application from suddenly encountering a schema it does not yet understand. It also makes it clearer how to respond to problems: often, you can stop the code change without immediately reverting a compatible expansion.

How to handle new columns, renames, and type changes

New columns: Add the column first and deploy code that can work even if it does not yet contain the expected value. Then begin writing to it and validate that new writes are being made correctly. If existing records need to be populated, treat that work as a separate operation: it may consume time and resources, so its progress should be monitored. Only after the data is ready and the code reads the new field should you consider removing the old one.

Renames: A direct rename is usually incompatible with versions that still query the old name. It is safer to add a column with the new name, coordinate writes temporarily, and move reads in stages. Remove the old name only after confirming that no active version or dependent process uses it.

Type changes: Do not assume a conversion will preserve every value. Define what happens to data that does not fit, check samples, and plan a conversion that can be controlled and verified. If the new type requires stricter rules, validate existing data before applying them.

Constraints and required values: Making a column mandatory can fail if rows still lack a value or writers do not provide one. Prepare the data and code first; then apply the constraint when you can demonstrate that it is satisfied. The exact sequence depends on the database engine and how it handles validation and locks.

Validations before, during, and after deployment

A successful migration is not defined solely by whether the statement completed. Check that functional and operational behavior remains correct. Before the change, record the relevant baseline so you can compare it later, and define which signals would justify stopping the deployment.

  • Compatibility: Confirm which application versions will be active at each stage, and test that they can work with the corresponding schema.
  • Data: Check relevant counts, null values, and business rules before and after the change. For a transformation, compare the result with known criteria, not just the absence of errors.
  • Performance: Monitor migration duration, locks, load, and latency for affected operations. If they worsen, stop the next phase and assess the impact before continuing.
  • Errors: Review query failures, application exceptions, and errors in integrations or scheduled jobs. The absence of alerts alone does not prove that every consumer has been accounted for.
  • Use of the old schema: Search code and queries for references, and check over an appropriate period whether the field to be removed is still being read or written.

Rehearse the procedure in a representative environment where possible, including the verification steps. A rehearsal can reveal dependencies and slow operations, although it cannot guarantee that production has the same workload or data distribution. Also make sure the migration can be identified and that the team knows who decides whether to continue, pause, or revert.

Rollback, forward fixes, and limitations

Reverting a migration is not always safe. An additive expansion is often easier to leave in place than to undo, especially if the application has already written data to the new column. Removing that data to return to the previous state can cause information loss. For this reason, a rollback plan should explain what will be reverted—the code, the schema, or both—and which data will be retained.

If new writes cannot be reliably translated to the old schema, it may be wiser to stop the deployment and prepare a forward fix. Define the decision criteria beforehand, such as errors in critical operations, sustained degradation, or inconsistent data. Do not promise an immediate rollback without first testing whether it is viable for that specific change.

Checklist for planning the change

Checklist for planning the change
  • Have all readers, writers, and processes that use the affected schema been identified?
  • Is the initial change compatible with the versions that will remain active during deployment?
  • Have schema expansion, the code transition, and removal of the old element been separated?
  • Has the source of truth been defined if two representations are temporarily maintained?
  • Have data, error, and performance checks been established, with owners and criteria for pausing?
  • Does the plan explain what can be reverted and when a forward fix will be needed?

To answer the question of how to change a database schema without stopping the service, think in terms of compatibility across stages, not an isolated modification. Add before removing, keep coexisting versions operational, and validate each transition using observable signals. When a change cannot be made additively, identify the risk explicitly and prepare an appropriate maintenance window or recovery strategy; do not assume every change can be deployed without interruption.

Fuentes y referencias

  1. MDN Web DocsMozilla
  2. Web performanceweb.dev
  3. OWASP Cheat Sheet SeriesOWASP Foundation