Library→First Way: Flow→Tools & Techniques→Database Change Management
Database Change Management
Databases are the hardest part of continuous delivery. How to version-control schema changes, apply them safely, and deploy without downtime.
Video Lesson
A video lesson for this topic is in development. The library articles and mission exercises cover the same material in the meantime.
The database problem
Application code is stateless: you can deploy a new version, roll back, and redeploy without consequence. Databases are stateful: every schema change must be applied to existing data, must be backwards compatible with the current application version, and cannot be rolled back the same way code can.
This asymmetry makes databases the most common source of deployment risk. A rename of a column breaks every query that references the old name. Dropping a column that is still referenced causes a runtime error. Adding a NOT NULL column without a default fails inserts from old application code.
The most dangerous deployment is one that includes both a schema change and application code that depends on it. If the schema migration runs, then the application deployment fails and rolls back — the application is now running against a schema it was not designed for.
Evolutionary database design
Martin Fowler and Pramod Sadalage's evolutionary database design treats the schema as a living artifact that changes incrementally — just like application code. The key principles:
Additive changes only
Add columns and tables. Never rename or remove them in the same deployment as the code that uses the new name. Renames are two-step: add new, migrate, remove old.
Backwards compatibility
Every schema change must be compatible with the current running application version. The new schema works with the old code. This enables zero-downtime deploys.
Small, frequent changes
One logical change per migration. Small migrations are easy to review, easy to rollback, and have a small blast radius if they fail.
Version control the schema
Every schema change is a versioned migration script in source control, reviewed in a PR, applied by the pipeline — never applied manually to production.
Database migrations
A migration is a versioned SQL script that transforms the schema from one known state to the next. Migration tools like Flyway and Liquibase track which migrations have been applied and apply the pending ones in version order.
# Flyway migration file naming convention
V1__create_users_table.sql
V2__add_email_index.sql
V3__add_premium_tier_column.sql
# V3__add_premium_tier_column.sql
ALTER TABLE users
ADD COLUMN tier VARCHAR(20)
NOT NULL DEFAULT 'free';
Flyway
SQL-first. Migrations are plain SQL files. Simple and predictable. Best for teams that know SQL and want minimal abstraction.
flyway migrate — applies pending migrations
Liquibase
XML/YAML/JSON changelogs with database-agnostic syntax. More powerful rollback support. Best for multi-database environments.
liquibase update — applies pending changesets
Zero-downtime migrations
The expand-contract pattern makes schema changes safe for zero-downtime deploys. Instead of a single breaking change, the migration is split into backwards-compatible steps that can be deployed independently:
Expand
Add new_email column (nullable)
Old code still works — reads old_email. New code can start writing new_email.
Migrate
Backfill new_email from old_email
Background job copies data. Both columns exist. Both apps work.
Switch
Deploy code reading new_email
New code in production. Old column still present as safety net.
Contract
Drop old_email column
Only after confirming no code reads old_email. Safe to remove.
Each step can be deployed independently. If any step fails, only that step needs to be addressed — the application continues to function because each step maintains backwards compatibility with the current code version.
Common anti-patterns
Big bang migration
Applying hundreds of schema changes in a single migration. If it fails partway through, the database is in an unknown state. Migrate incrementally — one logical change per script.
Direct production changes
Running ALTER TABLE in a production database console. Not version-controlled, not reviewed, not reproducible. Guaranteed to diverge from other environments eventually.
No rollback plan
Dropping a column or renaming a table without a tested rollback procedure. Many DDL operations cannot be rolled back automatically — you need a reverse migration ready.
Coupled schema + code deploy
Deploying a schema change and the code that depends on it in the same deployment. If the code deploy fails and rolls back, the application is running against the new schema with old code.
Unlocked migration files
Modifying a migration file after it has been applied to any environment. Flyway will detect the checksum mismatch and refuse to proceed. Treat applied migrations as immutable.
Further reading
Evolutionary Database Design — Fowler & Sadalage
martinfowler.com/articles/evodb.html. The foundational article on treating database schema as evolutionary, version-controlled artifacts.
DevOps Handbook — Chapter 15
Enable and Practice Continuous Integration of Database Changes. Migrations in the pipeline, zero-downtime patterns.
Flyway Documentation
flywaydb.org/documentation. Complete reference for Flyway migrations, versioning conventions, and pipeline integration.
Liquibase Documentation
docs.liquibase.com. Complete reference including rollback strategies, changeset authoring, and diff-based migrations.