Zero-Downtime Database Migrations

This concept applies to all deployments of `pandurart` that involve database schema changes. Schema migrations are executed by the dedicated db-updater service using Liquibase ↑LIQUIBASE, which is the only component with write access to the schema.

Rolling updates and canary deployments require that old and new application versions run in parallel against the same database. A breaking schema change β€” such as renaming or removing a column β€” would cause one of the two versions to fail.

The solution is the Parallel Change pattern ↑PARALLEL-CHANGE, also known as Expand and Contract or Zero-Downtime Database Migration ↑EVODB. It decomposes every potentially breaking schema change into a sequence of fully backward-compatible steps.

1. πŸ” The Three-Phase Pattern

1.1. Phase 1 β€” Expand

The schema is extended in a backward-compatible way. New columns, tables, or indexes are added alongside the existing structures. No existing column is removed or renamed. The old application version continues to work unchanged.

Example Liquibase changeset β€” Expand
<changeSet id="2024-06-01-001-expand" author="migration">
    <!-- add new column; nullable so the old app can still INSERT without it -->
    <addColumn tableName="events">
        <column name="location_url" type="VARCHAR(2048)"/>
    </addColumn>
</changeSet>

1.2. Phase 2 β€” Migrate / Transition

A new version of the application is deployed. It writes to both the old and the new column simultaneously and reads from the new column. During the rollout window, old instances still write only to the old column, so both columns must be kept consistent.

If existing rows must be backfilled, a data-migration changeset can be executed once all old instances are drained:

Example Liquibase changeset β€” Backfill
<changeSet id="2024-06-01-002-backfill" author="migration">
    <sql>
        UPDATE events
           SET location_url = website
         WHERE location_url IS NULL
           AND website IS NOT NULL;
    </sql>
</changeSet>

1.3. Phase 3 β€” Contract

Once all application instances run the new version and the old column is no longer written to or read from, a follow-up deployment removes it.

Example Liquibase changeset β€” Contract
<changeSet id="2024-07-01-001-contract" author="migration">
    <dropColumn tableName="events" columnName="website"/>
</changeSet>
The Contract changeset is intentionally released in a separate deployment from the Expand changeset β€” never in the same release.

3. βœ… Rules to Follow

βœ…

Always add new columns as nullable or with a default value in the Expand phase.

βœ…

Keep the Expand and Contract changesets in separate releases (separate Git tags / deployments).

βœ…

Execute db-updater before deploying the new application version.

βœ…

Ensure all old instances are terminated before running the Contract changeset.

βœ…

Each Liquibase changeset must be idempotent β€” never modify an already-applied changeset.

❌

Never rename a column directly β€” use Expand (add new) β†’ Migrate β†’ Contract (drop old).

❌

Never change the data type of an existing column in a single step.

❌

Never add a NOT NULL constraint without a default value while old instances are still running.

4. 🏷️ Naming Convention for Changesets

Changeset IDs follow the convention:

<ISO-date>-<sequence>-<phase>

Examples:

  • 2024-06-01-001-expand

  • 2024-06-01-002-backfill

  • 2024-07-01-001-contract

This makes the migration history self-documenting and allows teams to identify which phase a changeset belongs to at a glance.

5. ⚠️ Relationship to Canary and Rolling Deployments

During a canary deployment, both the old (v1) and the new (v2) application version process live traffic simultaneously. The database schema must be valid for both versions at the same time.

canary-db

The Expand phase is the enabler: it guarantees that both v1 and v2 are compatible with the current schema throughout the entire canary window.

6. πŸ“š Further Reading and References

  • ↑PARALLEL-CHANGE β€” Martin Fowler: ParallelChange β€” the canonical description of the pattern.

  • ↑EVODB β€” Martin Fowler & Pramod Sadalage: Evolutionary Database Design β€” the broader methodology behind schema evolution.

  • ↑REFACTORING-DB β€” Scott Ambler & Pramod Sadalage: Refactoring Databases β€” the reference book with a complete catalogue of database refactoring patterns.

  • ↑LIQUIBASE β€” Liquibase: official documentation for the migration tool used by db-updater.

  • ↑LIQUIBASE-ZDDT β€” Liquibase: Zero-Downtime Deployments with Liquibase.

  • ↑EXPAND-CONTRACT β€” Martin Fowler: Expand Contract β€” a concise summary of the two outer phases.