Kubernetes環境でDBスキーマ変更を止めずに進める:ゼロダウンタイム移行の実践戦略
「カラムを追加するだけだから大丈夫」──この油断が、本番障害の入口になります。Kubernetes のように複数バージョンの Pod が同時に存在する環境では、DB スキーマ変更はアプリ変更よりも慎重に扱う必要があります。
本記事では、Expand-Contract パターンを中心に、ゼロダウンタイムを目指すための具体手順を解説します。実際の運用では、DDLの速さより「互換性のある期間をどう作るか」が勝負です。
1. なぜKubernetesでDB移行が難しいのか
Kubernetesでは、ローリングアップデート中に新旧Podが混在します。つまり次の状態が同時に発生します。
- 新アプリは新スキーマを期待
- 旧アプリは旧スキーマしか知らない
- DBは1つしかない
このとき破綻するのが「破壊的変更を先に適用する」ケースです。たとえば旧カラムを即削除すると、旧Podがエラーを連発します。
2. 基本戦略:Expand → Migrate → Contract
ゼロダウンタイム移行の原則はこの3段階です。
- Expand: 互換性を壊さない変更を先に入れる(新カラム追加など)
- Migrate: アプリを段階的に切替え、データを移行する
- Contract: 旧仕様を最終削除する(十分な監視後)
この順序なら、どの時点でも旧新どちらのアプリも動作可能にできます。
3. 具体例:users.full_name を first_name / last_name へ分割
3.1 Expand フェーズ
まず破壊的でないDDLを適用します。
|
|
この時点で旧アプリは full_name を使い続けられます。新アプリは新カラムに対応した実装を持っていても、まだ必須にしません。
3.2 アプリを「両対応」にする
書き込み時は両方へ保存(dual write)し、読み込み時は新カラム優先 + 旧カラムフォールバックにします。
|
|
この両対応期間を作るのが、ゼロダウンタイムの本質です。
3.3 バックフィル(既存データ移行)
大規模テーブルでは一括更新を避け、チャンク更新します。
|
|
ジョブ実装時のポイント:
- 1チャンクごとに commit
- 再開可能なチェックポイント(last_id)を保存
- 実行時間帯を制御(ピーク時間回避)
3.4 Contract フェーズ
全Podが新実装になり、フォールバックが不要と判断できたら旧カラム削除へ進みます。
|
|
削除は必ず最後です。ここを急ぐとロールバック不能になります。
4. マイグレーション実行方式の選び方
Kubernetesでは主に3パターンがあります。
A. CI/CDで先行実行(推奨)
デプロイ前に migration Job を走らせ、成功後にアプリを更新。
- メリット: 実行順序を固定しやすい
- デメリット: 長時間 migration の扱いが難しい
B. initContainer 実行
Pod起動時に migration を走らせる方式。
- メリット: 実装が単純
- デメリット: 複数Pod同時起動で競合しやすい
C. 専用 Migration Controller / Job
Argo Workflows などで明示的に管理。
- メリット: 大規模運用で監査しやすい
- デメリット: 初期構築コストが高い
中規模までなら A が最も事故が少ないです。
5. Alembic/Flyway運用の実務ポイント
5.1 1 migration = 1責務
「追加 + データ移行 + 削除」を1ファイルに詰め込むと失敗時に戻しづらくなります。Expand/Migrate/Contract を別 migration に分けて、各段階を検証可能にしてください。
5.2 lock timeout を設定する
DDL はロック待ちでアプリを止めることがあります。PostgreSQL なら lock_timeout を設定して、危険な待機を避けます。
|
|
失敗時は即中断し、メンテナンス時間帯に再実行する判断が可能です。
5.3 破壊的変更前に計測窓を設ける
旧カラム参照がゼロになったことを、メトリクスやログで一定期間確認してから削除します。目安として 7〜14日程度の観測期間を取ると安全です。
6. ロールバック戦略
ゼロダウンタイム設計では、ロールバック可能性を最初に決めます。
- Expand段階: ほぼ即ロールバック可能
- Migrate段階: dual write を維持していればロールバック可能
- Contract段階: 削除後は復元コスト高(バックアップ依存)
したがって、Contract 実施前に以下を満たす必要があります。
- 直近スナップショット取得済み
- 復元手順を演習済み
- 影響範囲(API・バッチ・BI)を棚卸し済み
7. 監視項目と成功判定
移行中は「成功したか」より「安全に進んでいるか」を見ます。
- APIエラー率(4xx/5xx)
- DBロック待ち時間
- スロークエリ件数
- migration ジョブ進捗(処理済み件数、残件数)
- 旧カラム参照回数
成功判定の例:
- 新旧Pod混在中のエラー率に有意な悪化なし
- バックフィル完了率100%
- 旧カラム参照0が連続7日
- Contract後24時間で異常なし
8. よくある失敗と回避策
失敗1: 非NULL制約を早く付けすぎる
バックフィル完了前に NOT NULL を付けると、旧データで失敗します。まずは nullable で追加し、データ移行後に制約追加が正解です。
失敗2: インデックス作成で書き込み停止
大きいテーブルで通常インデックス作成を行うとロックが重くなります。PostgreSQL では CREATE INDEX CONCURRENTLY を使って影響を下げます。
失敗3: migration の実行主体が複数
同時に2つのJobが走ると競合します。Kubernetes Job は排他制御(Lease/Lock)を持たせるか、CI側で単一実行を保証してください。
9. 実運用テンプレート:Migration Job と段階リリース
最後に、現場でそのまま使える最小テンプレートを示します。ポイントは「DDLを一気にやらない」「アプリ側フラグで読取切替を制御する」の2点です。
9.1 Migration Job(Expand専用)
|
|
Expand 用 Job を分離しておけば、失敗時にアプリデプロイを止める判断が明確になります。backoffLimit: 0 にして「失敗を隠さない」運用にするのも実務で有効です。
9.2 段階リリース手順(例)
- Expand Job 実行(DDLのみ)
- アプリ v1(dual write + fallback read)を10%配信
- エラー率・ロック待ちを30分監視
- 50% → 100%へ段階拡大
- バックフィル Job 実行
- 旧参照ゼロ確認後に Contract 実施
Kubernetes では kubectl rollout status deployment/api -n prod を必ず監視に組み込み、配信完了判定を人間が明示的に確認する運用が安全です。
まとめ
KubernetesでのDBマイグレーションは、DDLテクニックだけでは成功しません。重要なのは、
- 互換性期間を意図的に作る
- 段階を分けて進める
- ロールバック可能性を先に設計する
- 監視で「削除してよい」根拠を取る
この4点です。Expand-Contract を守るだけで、移行の失敗率は目に見えて下がります。スキーマ変更は怖い作業ですが、手順化すれば再現可能な運用にできます。次回の変更から、ぜひこの流れで試してみてください。