Phase 5: Platform Engineering

Schema migrations: zero-downtime strategies & rollback plans

Advanced ~3 min read
Think of it this way A friendly analogy. Read this if the technical version feels dense. Show Hide

Imagine you're building a super cool LEGO city, and your friends are already playing in it. They're moving their LEGO people into houses, driving cars, and using all the buildings. Now, you want to add a special new room to the police station – maybe a cool jail cell. But here's the tricky part: you don't want to tell everyone to stop playing or wait while you make the changes. That would be boring! You want to upgrade the city while everyone is still having fun, without anyone noticing a pause. This is what grown-up engineers think about when they update how information is organized in big computer systems, aiming to keep everything running smoothly.

So, how do you add that new jail cell without stopping the game? You don't just rip out the old wall! Instead, you use a clever trick. First, you expand the police station by carefully adding the new jail cell next to where the old storage room was. Your friends still use the old storage room because they don't know about the new jail yet. Then, you tell some of your friends who are setting up the police station to start putting new "bad guys" into both the old storage room and the new jail cell. Everyone playing can still find things in the old spot, but new things are also going into the new spot.

After a while, once everyone playing knows about the awesome new jail cell and is using it, you can finally remove the old, unused storage room pieces. See? You slowly added the new part, then slowly moved everyone to use the new part, and only then did you take away the old part. This "expand and contract" idea means engineers can safely add new features to a huge online game or a website, like adding a new space for "customer reviews" or a way to track "shipping details" without anyone experiencing a slow down or a moment where the website isn't working.

This method is super important because it lets computer systems grow and improve constantly, just like your LEGO city. It means that when you eventually build your own amazing apps or games, you can always add cool new features or make improvements without ever having to shut down or make your users wait. You can keep everyone happy and playing, all the time!

Schema migrations are fundamental to evolving database structures. For an SRE, the critical challenge is performing these changes—like adding columns, modifying types, or restructuring tables—on a live production database without introducing downtime or application errors. The core principle involves decoupling the schema change itself from the application deployment, ensuring that both old and new application versions can coexist and operate correctly with the evolving schema for a period. This approach mitigates common risks such as schema locking, application-database incompatibility, and data corruption, which can otherwise severely impact availability.

The most robust strategy for zero-downtime schema migrations is the "Expand and Contract" pattern, often combined with "dual writes." First, in the "expand" phase, you add new structures (e.g., a new nullable column) while retaining the old ones. Applications continue using the old schema. Next, a background process might backfill data into the new structures. Then, during an application deployment, the application is updated to "dual write" data to both the old and new structures, and potentially read from the new one. After validating the new schema and application interaction, the "contract" phase involves removing the deprecated old structures. For large-scale MySQL environments, tools like gh-ost or pt-online-schema-change abstract much of this complexity by using logical replication and triggers to perform non-blocking table alterations.

A meticulously planned and tested rollback strategy is paramount. During the expand phase, if a critical issue arises, rolling back typically involves reverting the application deployment to use the old schema exclusively. If dual writes were correctly implemented, the application can revert without impacting data integrity in the old schema. Rolling back database schema changes themselves is more complex and potentially destructive; ideally, your initial "expand" steps are additive (e.g., adding a nullable column) and easily reversible by dropping the new column, or by keeping the old structures available until the new ones are proven stable. Always test rollback procedures thoroughly in pre-production environments to ensure they are viable and don't introduce further data loss or inconsistency.

Key Takeaways

  • Implement the "Expand and Contract" pattern with dual writes for robust zero-downtime migrations.
  • Decouple schema changes from application deployments to maintain backward compatibility.
  • Leverage specialized tools like gh-ost or pt-online-schema-change for complex table alterations.
  • Always design and rigorously test a clear rollback plan, prioritizing additive schema changes.
  • Ensure temporary backward compatibility, allowing older application versions to function during migration.

Code Example

sql
-- Phase 1: Expand - Add new nullable column
ALTER TABLE users
ADD COLUMN email_hash VARCHAR(64) NULL;

-- Phase 2: Data Migration (e.g., a background job)
-- This process runs async, populating email_hash for existing users.
-- UPDATE users SET email_hash = SHA2(email, 256) WHERE email IS NOT NULL;

-- Phase 3: Application Deployment (start dual-writing and reading new column)
-- Application logic now writes to both 'email' and 'email_hash'.
-- Reads might prefer 'email_hash' if present, falling back to 'email'.

-- Phase 4: Enforce NOT NULL constraint after data migration and app validation
ALTER TABLE users
ALTER COLUMN email_hash SET NOT NULL;

-- (Contract phase, dropping 'email' column, happens much later)

How this code works

This SQL code demonstrates a multi-phase strategy to add a new email_hash column to the users table without causing application downtime. The goal is to safely introduce a new data field, populate it, and then enforce its mandatory status, all while the application remains fully operational.

The process begins with ALTER TABLE users ADD COLUMN email_hash VARCHAR(64) NULL;. The subtle but critical choice here is adding the column as NULL. This allows the column to be added instantly without requiring a full table rewrite or locking the table, which would cause downtime if the table is large. It also means existing rows don't immediately need a value, preserving application availability.

Next, a Data Migration (like an UPDATE users command run as a background job) asynchronously populates email_hash for existing rows. After this data is filled and the application code is deployed to start dual-writing (writing to both email and email_hash) and reading from the new column, the final database step is ALTER COLUMN email_hash SET NOT NULL;. This enforces that all new data inserted into the table must have an email_hash, and confirms that all existing data has been successfully migrated. The email column can then be safely dropped much later in a "contract" phase.