Changing data formats in a running service happens regularly. Column encryption, type changes, JSON schema updates, normalization or denormalization. A big-bang approach that stops the service for a single cutover is risky. If something goes wrong during the transition, the entire service goes down.
Combining dual write with fallback read enables format transitions without service interruption. The key is maintaining a rollback-safe state at each step.
Dual Write + Fallback Read
Dual write stores data in both the old format and the new format. During the transition period, the same data coexists in both places.
Fallback read uses the new format if a value exists; otherwise it falls back to the old format. Data not yet converted to the new format is still readable.
flowchart TD
W["Write"] --> W1["Store in old format"]
W --> W2["Store in new format"]
R["Read"] --> C{"New format
has value?"}
C -->|"Yes"| D["Use new format"]
C -->|"No"| E["Use old format"]
Combining these two creates a transition period where old and new data coexist.
Three-Step Process
The transition splits into three steps. Each step proceeds only after the previous one has been deployed.
flowchart LR
S1["Step 1: Preparation
Add new format
Start dual write
Apply fallback read"]
S2["Step 2: Migration
Convert existing data
to new format
dry-run → execute"]
S3["Step 3: Cleanup
Remove fallback
Drop old format"]
S1 -- "Deployed" --> S2 -- "Verified" --> S3
Step 1: Preparation
Add the new format and modify the code.
- Schema change: Add new columns or fields. Start as nullable since existing data does not have the new format yet.
- Dual write: On INSERT and UPDATE, write values to both old and new formats.
- Fallback read: On SELECT, use the new format if a value exists; otherwise return the old format’s value.
Once deployed, new data is stored in both places. Existing data remains only in the old format, handled by fallback read.
Rollback: Ignore the new format and everything works as before. Just revert the code change.
Step 2: Migration
Batch-convert existing data to the new format.
Write and run a batch script. Find rows where the new format is empty, convert the old format value, and store it in the new format.
Run dry-run first. Check target row count and estimated duration. For large datasets, adjust batch size to manage DB load.
After execution, verify. Confirm that new format values match old format values. Cross-check total row counts. Verification often takes more time than the migration itself.
Rollback: The fallback read from Step 1 is still active. Any issues with the new format automatically fall back to the old format.
Step 3: Cleanup
After migration is complete and verified, remove the old format and fallback logic.
- Data verification: Confirm no nulls or empty values in the new format once more. Check that data inserted after Step 2 is also in the new format.
- Code cleanup: Remove dual writes and fallback branches. Consolidate to use only the new format.
- Schema cleanup: Drop old format columns or fields.
No rollback: Dropping the old format deletes the original data. This is why thorough verification is essential.
Use Cases
This pattern is not limited to DB column encryption. Any situation where data format changes and the service cannot stop follows the same structure.
Column encryption. Add an encrypted column next to the plaintext column, dual-write to both, batch-encrypt existing plaintext, then drop the plaintext column.
Column type change. varchar(100) → text, int → bigint. Add the new-type column, transition via dual write + fallback, then drop the old column.
JSON schema change. When renaming keys or restructuring a JSON column, create a transition period that supports both old and new structures simultaneously.
Normalization / denormalization. When adding denormalized columns to reduce joins, or splitting data into separate tables for normalization, the dual write + fallback structure applies.
Cost of the Pattern
This pattern provides safety, but a cost comes with it.
Code complexity increases during the transition period. Dual write and fallback branches are added throughout the service code. This code is removed in Step 3, but during the transition it increases review and maintenance burden.
If multiple targets need transition, this cost repeats. Ten tables means applying the same pattern ten times. Since the structure is identical across repetitions, automation becomes a consideration.
The conditions for this pattern are clear: multiple services reference the same data, large data volumes exist, and service downtime is not acceptable. Outside these conditions, scheduling a maintenance window for a single cutover may be simpler.
References
- Envelope Encryption — Covers the key management structure that pairs with column encryption transitions.