DevOps & Cloud

Database Migration Strategies for Zero-Downtime Deployments

February 22, 2026 2 min read 7 views

Database migrations in production are the riskiest part of any deployment. The expand-contract pattern ensures zero-downtime schema changes.

The Expand-Contract Pattern

Phase 1: EXPAND — Add new column/table alongside old one
Phase 2: MIGRATE — Backfill data, update app to write to both
Phase 3: CONTRACT — Remove old column/table after full cutover

Example: Renaming a Column Safely

// WRONG: Breaks running application
Schema::table('users', function (Blueprint $table) {
    $table->renameColumn('name', 'full_name');
});

// RIGHT Phase 1: Add new column
Schema::table('users', function (Blueprint $table) {
    $table->string('full_name')->nullable();
});

// Phase 2: Backfill data
DB::statement('UPDATE users SET full_name = name WHERE full_name IS NULL');

// Phase 3: After code is updated, drop old column
Schema::table('users', function (Blueprint $table) {
    $table->dropColumn('name');
});

Safe Migration Practices

// Always add columns as nullable or with defaults
$table->string('timezone')->default('UTC');
$table->string('avatar_url')->nullable();

// Use batched updates for large tables
User::whereNull('full_name')->chunkById(1000, function ($users) {
    foreach ($users as $user) {
        $user->update(['full_name' => $user->name]);
    }
});

Rules for Production Migrations

  • Never rename or drop columns in the same deploy as code changes
  • Always make schema changes backward-compatible
  • Test migrations against a copy of production data
  • Set statement timeouts to prevent long-running locks
  • Always write a down() method for rollback
Share this post:

Related Posts

Comments (0)

Please log in to leave a comment. Log in

No comments yet. Be the first to comment!