
Database refactoring often presents a significant risk to high-availability systems. When a schema change requires both structural updates and data migration, a traditional "stop-the-world" approach leads to unacceptable downtime. The Expand-Migrate-Contract pattern (also known as Parallel Change) solves this by splitting a single breaking change into multiple additive and non-breaking phases.
This methodology ensures that the database remains compatible with current application versions throughout the entire process. By avoiding massive, all-or-nothing deployments, engineering teams can verify each step, roll back if necessary, and maintain a consistent experience for downstream systems and end-users alike.
Phase 1: The Expand Phase
The first step involves adding the new structure to the database without removing or altering the existing one. For example, if we are splitting a large table into two smaller ones, we create the new tables and relationships alongside the original. During this phase, no application logic changes for reads, but we might start preparing the environment for dual operations.
- Introduce new columns or tables with proper constraints.
- Maintain existing triggers, views, or stored procedures to ensure backward compatibility.
- Avoid deleting any legacy fields until the entire cycle is complete.
Phase 2: The Migrate Phase
Once the new structure is in place, we begin the transition. This is the most critical stage, as it involves keeping the old and new data synchronized. We update the application to "dual-write"—saving data to both the old and new structures simultaneously. This ensures that any new records are available in both formats.
While dual-writing handles new data, we perform a background migration (backfilling) for historical records. This process moves older rows from the legacy structure to the new one in small batches to avoid locking tables or saturating database performance. We then switch the application to read from the new structure, verifying that the results match expectations.
Phase 3: The Contract Phase
The final phase begins only after we are 100% confident that the new structure is the system of record and that no applications or reporting tools are still accessing the old schema. We remove the dual-write logic from the code, effectively making the new structure the sole destination for data.
After a cooling-off period where we monitor logs for any unexpected legacy access, we safely drop the old columns or tables. This contracts the database back to its optimized state, completing the refactoring without a single minute of downtime. It is a disciplined approach that prioritizes reliability over speed, ensuring that complex schema evolution remains a routine engineering task rather than a high-stakes emergency.
Comments & Discussion
This could be your first comment. Share your thoughts on phased refactoring below.
Leave a message