要点: 安全な変更では、古いアプリケーションと新しいアプリケーションが一定時間共存すると考えます。
この記事の用語: Expand–Contract:新構造を追加して移行後に旧構造を削除する手順。Backfill:過去データを新構造へ補う処理。
現場の課題
列名の変更や厳しい制約を一度に入れると旧版が停止します。大きな更新は長時間ロックを保持するため、変更を戻せる小さな段階へ分ける必要があります。
単純な対策だけでは不十分な理由
デプロイ中は旧版と新版が共存します。Expand–Contractは、追加、互換書き込み、Backfill、読み取り切替、旧構造削除へ分け、各段階を戻せるようにします。
処理フロー
互換スキーマ追加→Migrate
Dual 読み取り・書き込み→Verify
Backfillと照合→Contract
旧経路削除
アーキテクチャ上の判断
Additive Expand
RenameはAdd、Copy、切替、Removeに分解します。
再開可能Backfill
安定キー Range、小Commit、Checkpointを使います。
制約を別途Validate
失敗扱いingを抑えて既存Rowを検証します。
さらに深く考える
ロールバックはデータ問題
新形式だけのデータがある間はReverse Compatibilityを維持します。
制約で保証
照合後にDB 制約で前提を強制します。
実装手順
- 現行動作を変えずに、NULL許容列または新テーブルを追加します。
- 旧版と新版の両方で動くコードをデプロイし、再開可能なバッチでBackfillします。
- フィーチャーフラグで読み取りを切り替え、ロールバック不要を確認した後のリリースで旧構造を削除します。
コード例: 再開可能なPrimary-キー Backfill
lastId = checkpoint.load('account_region_v2')
rows = SELECT id FROM accounts WHERE id > :lastId ORDER BY id LIMIT 1000
UPDATE accounts SET region_v2 = derive(region) WHERE id IN (:rows)
checkpoint.save(MAX(rows.id))
sleep(rateLimit)各バッチ後にレプリカ遅延とLock Waitを監視し、顧客影響前に自動停止します。
想定しておく障害
- 旧ワーカーがLegacy Fieldだけを書きます。
- Backfillが新しいOnline 書き込みを上書きします。
- 旧Column削除がレポートを壊します。
監視すべきこと
| 指標 | 何が分かるか |
|---|---|
| 旧・新読み取り/書き込み数 | Compatibility利用を確認します。 |
| Backfill進捗・Mismatch | 完全性と変換精度です。 |
| Lock Wait・レプリカ Lag | 顧客トラフィックを保護します。 |
設計の検証方法
- 旧・新バージョンを同時実行します。
- 任意CheckpointでBackfillを再開します。
- 同時Updateを上書きしないか確認します。
本番導入の進め方
Expand、互換アプリケーション、Backfill、切替、Contractを別リリースにします。少数テナントから読み取りを切り替え、ロールバック期間と監査結果を確認してから旧構造を削除します。
本番前チェックリスト
- 実Volume相当でDDL Lockを確認します。
- Backfillにレート制限、Checkpoint、一時停止を持たせます。
- 旧経路Usageゼロを確認してContractします。
まとめ
無停止マイグレーションは、賢いSQL一文ではなく、互換性があり戻せる変更の連続です。
