Database/Schema migrations

Database Schema Migrations and Compatibility

Understanding Schema Migrations

Schema migrations are controlled changes to your database structure over time. Unlike schema-on-read databases that can handle mixed data formats, relational databases enforce a single schema at any point in time. This creates unique challenges when evolving your application.

As the text notes, in large applications, code changes cannot happen instantaneously. You might have:

  • Multiple application servers running different versions during deployment

  • Mobile apps that users haven't updated yet

  • Background workers processing old jobs

  • Long-running transactions that started before the migration

This is where backward and forward compatibility become critical.

Backward vs Forward Compatibility

Backward Compatibility: New code can work with old schema

  • Your updated application code can handle the database structure from before the migration

Forward Compatibility: Old code can work with new schema

  • Your existing application code continues working after the schema changes

Common Migration Patterns

1. Adding a Nullable Column (Safe ✓)

This is the safest migration because it's both backward and forward compatible.

2. Adding a NOT NULL Column (Requires Multi-Step)

This requires a careful, multi-phase approach:

3. Renaming a Column (Expand-Contract Pattern)

The safest approach uses three phases:

4. Removing a Column (Two-Step Process)

5. Changing Column Type (Complex Migration)

Migration Tools and Best Practices

Using Migration Libraries

Key Principles

  1. Never break backward compatibility immediately - Old code must continue working

  2. Use multi-phase migrations for risky changes (add → migrate → remove)

  3. Always have a rollback plan - migrations should be reversible

  4. Test migrations on production-like data - edge cases matter

  5. Make schema changes additive first - expand, then contract

  6. Deploy code before schema for removals - deploy after schema for additions

  7. Monitor during migrations - watch for errors in production

The Expand-Contract Pattern

This is the gold standard for zero-downtime migrations:

  1. Expand: Add new schema elements (columns, tables) without removing old ones

  2. Migrate: Update application code to use both old and new

  3. Contract: Remove old schema elements after all code is updated

This ensures that at every step, both old and new application versions can coexist, which is exactly what you need when "code changes often cannot happen instantaneously" in large applications.


Python tools: Alembic, Django migrations, Yoyo-migrations, Atlas

Golang tools: Goose

Last updated