Skip to content

Zero-Downtime Database Migrations: Flyway vs. Liquibase

CoreConceptAugust 3, 20269 min read

Deploying application updates without taking down production databases requires decoupling database schema evolution from application code deployments. Performing destructive schema changes — such as renaming table columns or dropping fields — breaks old application instances still serving active user traffic during rolling updates.

Achieving zero-downtime schema updates requires structured migration engines and phased migration patterns. Flyway and Liquibase are the leading open-source database migration tools. This guide compares Flyway's SQL-first versioning against Liquibase's declarative XML/YAML changesets, while detailing the Expand-Contract Pattern for zero-downtime production deployments.

Zero-downtime database schema migration architecture comparison between Flyway and Liquibase
Zero-downtime database schema migration architecture comparison between Flyway and Liquibase

Mental Model: The Expand-Contract Database Schema Migration Pattern

The core principle of zero-downtime database migration is ensuring database backward compatibility across multiple running application versions. Never alter or drop active database structures in a single deployment.

The Expand-Contract (Parallel Change) Pattern splits schema evolution into three distinct deployment phases: 1. Expand: Add new columns or tables alongside old ones without modifying existing columns. Application Version N continues reading and writing to old structures, while write-triggers copy incoming data to new columns. 2. Transition: Deploy Application Version N+1, which writes to both old and new columns while reading exclusively from new structures. 3. Contract: After verifying Version N+1 stability, deploy Version N+2 to drop legacy triggers and old columns safely.

For database indexing and multi-tenancy optimization, read optimizing postgresql query performance explain analyze and multitenant saas postgres schema.

Expand-Contract zero-downtime database migration lifecycle across application versions
Expand-Contract zero-downtime database migration lifecycle across application versions

Quick reference

  • Expand Phase adds new columns or tables without breaking legacy application reads.
  • Transition Phase deploys application code configured to read from new schema structures.
  • Contract Phase cleans up obsolete columns, triggers, and temporary database views.
  • Prevents database lock contention and SQL syntax errors during rolling app deployments.
  • Supports immediate rollback to Version N if new code encounters production bugs.

Remember this

Apply the Expand-Contract pattern to execute backward-compatible schema changes across three phased deployments.

Flyway vs Liquibase: SQL Versioning vs Declarative XML/YAML Changesets

Choosing between Flyway and Liquibase depends on team preference for plain SQL scripts versus database-agnostic declarative changesets.

Flyway follows a SQL-first approach. Migrations are written as versioned raw SQL files (V1__create_users_table.sql, V2__add_email_index.sql). Flyway tracks applied migrations using a flyway_schema_history metadata table. It provides simplicity, full access to database-specific SQL features (like PostgreSQL CONCURRENTLY indexes), and zero abstraction overhead.

Liquibase uses database-agnostic XML, YAML, JSON, or SQL changesets. Liquibase abstracts DDL statements into declarative change types (createTable, addColumn), allowing identical changeset files to execute across PostgreSQL, Oracle, MySQL, and SQL Server. Liquibase tracks state in a DATABASECHANGELOG table and generates automated SQL rollback scripts (rollbackCount).

Quick reference

  • Flyway uses plain, versioned SQL files for direct database control and simplicity.
  • Liquibase uses declarative XML/YAML changesets portable across multiple RDBMS engines.
  • Flyway tracks execution using flyway_schema_history metadata checksum tables.
  • Liquibase automatically generates database rollback scripts for declarative change types.
  • Flyway supports Java and CLI execution natively inside Spring Boot and CI/CD pipelines.

Remember this

Use Flyway for SQL-first PostgreSQL projects, or Liquibase for multi-database enterprise applications requiring automated rollback scripts.

Managing Locks, Backward-Compatible Columns, and View Triggers

Executing DDL migrations on multi-gigabyte production tables risks table lock timeouts and application outages if not executed carefully.

Avoid ALTER TABLE ADD COLUMN ... DEFAULT 'value' on older PostgreSQL versions without DEFAULT optimization, as it rewrites the entire physical table while holding an ACCESS EXCLUSIVE lock. Always add nullable columns first, then populate default values in asynchronous background batches.

When renaming columns (e.g., phone to mobile_number), create the new mobile_number column and attach a PostgreSQL database trigger (BEFORE INSERT OR UPDATE) to synchronize writes automatically between both fields until legacy readers are decommissioned.

Expand-Contract zero-downtime database migration lifecycle across application versions
Expand-Contract zero-downtime database migration lifecycle across application versions

Quick reference

  • Create indexes using CREATE INDEX CONCURRENTLY to avoid blocking table read/write locks.
  • Add new columns as NULLABLE first before backfilling data in asynchronous background batches.
  • Use PostgreSQL triggers to keep legacy and new column values synchronized during transition phases.
  • Set lock_timeout parameters (e.g., SET lock_timeout = '5s') to prevent DDL queries from hanging active connection pools.
  • Test schema migration execution times on staging database snapshots before running in production.

Remember this

Execute non-blocking DDL statements with lock timeouts and use database triggers to sync parallel columns.

Automating Migration Verification Pipelines in CI/CD

Database migrations must be tested and verified inside continuous integration (CI/CD) pipelines before reaching production databases.

Integrate migration CLI tools into GitHub Actions or GitLab CI. In CI pipelines, spin up an ephemeral PostgreSQL Docker container, apply all historical migration scripts from scratch (flyway migrate), and execute dry-run schema validations (liquibase update-testing-rollback).

For production deployments, separate database migration execution from application pod startup. Run migrations in a pre-deployment Kubernetes Job or CI stage. This prevents race conditions where multiple application pods attempt to acquire migration locks simultaneously upon startup.

Quick reference

  • Test migration scripts against ephemeral PostgreSQL Docker containers in CI pipelines.
  • Validate backward migration rollback scripts using dry-run verification commands.
  • Execute production migrations inside dedicated Kubernetes Jobs before rolling app pod updates.
  • Prevent application startup lock contention by running migrations out-of-process.
  • Export migration history metrics to Grafana to track schema update duration trends.

Remember this

Run database migrations in pre-deployment CI/CD jobs against disposable database containers before releasing app pods.

Key takeaway

To test zero-downtime migrations, create an Expand-phase Flyway script adding a nullable column. Verify that current application instances write successfully without table lock errors.

Share:

Related Articles

Architecting multi-tenant Software-as-a-Service (SaaS) backend databases requires balancing strict data isolation agains

Read

PostgreSQL query optimization requires moving beyond intuition to inspect the actual execution plans generated by the Co

Read

Deploying new code directly to 100% of production users in a single release introduces massive risk. A single unhandled

Read

Keep learning

Follow a structured path or browse all courses to go deeper.