データマイグレーションが失敗したとき:実際に機能するロールバック戦略
本番環境にデータベースマイグレーションをデプロイしたとしよう。スクリプトは12分間実行され、3つのテーブルを変更し、カラム間でデータを移動した。そして最後のステートメントで失敗した。変更の半分は適用され、残り半分は適用されていない。アプリケーションは不完全なスキーマを期待してエラーを返し続けている。
この瞬間、多くのチームはデータマイグレーションのロールバックがアプリケーションデプロイのロールバックとは全く異なることに気づく。アプリケーションコードなら、バイナリやコンテナイメージを差し替えれば古いバージョンが再び動く。しかしデータの場合、古いコードを再デプロイしても削除したカラムは元に戻らない。カラムは消え、その中にあったデータも失われている。
データマイグレーションのロールバック戦略は、マイグレーションを開始する前に存在していなければならない。失敗した後に計画しても遅すぎる。
マイグレーション前のバックアップ、後ではない
最も信頼性の高いロールバック機構は、マイグレーション開始直前に取得したデータベースの完全スナップショットである。これは夜間バックアップではない。変更直前のデータの正確な状態を捉えたポイントインタイムコピーだ。
このバックアップはパイプラインの一部として自動化すべきである。マイグレーションステップが実行される前に、パイプラインはデータベースダンプ、スナップショット、またはリストアポイントを作成するレプリケーション停止をトリガーする。バックアップステップが失敗すれば、パイプラインは停止する。マイグレーションは実行されない。これにより、デプロイ前に手動バックアップを取り忘れるというヒューマンエラーを排除できる。
次のフローチャートは、マイグレーション失敗時の判断経路を示し、状況に応じた適切なロールバック方法の選択を支援します。
クラウドデータベースの場合、これは多くの場合、ボリュームのスナップショット取得やインスタンスのクローン作成を意味する。セルフホストデータベースの場合は、ダンプコマンドの実行やファイルシステムスナップショットの利用を意味する。重要なのは、バックアップが検証可能であることだ。リストアできないバックアップファイルはバックアップではない。
マイグレーションバージョンロールバックの限界
ほとんどのマイグレーションフレームワークは前方および後方バージョンをサポートしている。Flywayはmigrateとundo、Liquibaseはupdateとrollback、Alembicはupgradeとdowngradeと呼ぶ。これらのツールはダウンマイグレーションスクリプトを実行することでスキーマ変更を元に戻せる。
ただし、ダウンマイグレーションが安全に機能するのは可逆的な変更のみである。NULL許容カラムの追加は可逆的だ。ダウンマイグレーションでそのカラムを削除すればデータは失われない。カラム名の変更も、ダウンマイグレーションで元の名前に戻せば可逆的だ。しかし破壊的な変更は別の話である。カラムを削除した場合、ダウンマイグレーションでカラムを再作成できても、そのカラムにあったデータは失われている。データをある形式から別の形式に変換した場合、元の値をどこかに保存していなければ、ダウンマイグレーションで変換を元に戻せない。
バージョンロールバックは、誤った環境にデプロイされたマイグレーションや、クエリを壊すスキーマ変更など、早期のミスを発見するのに有用だ。しかしデータ損失に対するセーフティネットではない。ダウンマイグレーションだけに依存するのは、ロールバック時にデータ損失を引き起こすよくある誤りである。
セーフティネットとしてのポイントインタイムリカバリ
最も堅牢なロールバック戦略は、マイグレーションスクリプトにまったく依存しない。ポイントインタイムリカバリは、データベーストランザクションログを使用して、マイグレーション開始前の任意の時点にデータベースを復元する。
仕組みはこうだ。データベースはすべての変更を記録するトランザクションログまたは書き込み前ログを継続的に書き込む。これらのログとベースバックアップがあれば、特定のタイムスタンプまでログを再生できる。マイグレーションが14:00に失敗した場合、マイグレーション開始前の13:59にデータベースを復元する。マイグレーションによるすべての変更は消え、データベースはマイグレーションがどれほど破壊的であったかに関係なく元の状態に戻る。
ポイントインタイムリカバリには準備が必要だ。データベースはトランザクションログを継続的にアーカイブするように設定されていなければならない。チームは特定の時刻にリストアを実行するツールと権限を持っていなければならない。そしてプロセスは定期的にテストされなければならない。多くのチームは、インシデント発生時に初めてポイントインタイムリカバリの設定が壊れていることに気づく。
このアプローチは、破壊的なものを含むあらゆるマイグレーションで機能する。マイグレーションがカラムを追加したか、テーブルを削除したか、数百万行を変換したかは関係ない。単に時間を巻き戻すだけだ。
マイグレーションだけでなくロールバックもテストする
ステージング環境で全てのテストに合格したマイグレーションでも、予期しないデータ量、ロック競合、データのエッジケースにより本番環境で失敗することがある。ロールバックも同様だ。ロールバックが機能することを確認する唯一の方法は、テストすることである。
ステージング環境でマイグレーションを実行する。次に、各戦略(ダウンマイグレーション、マイグレーション前バックアップ、ポイントインタイムリカバリ)を使ってロールバックを試みる。各方法の所要時間を測定する。ポイントインタイムリカバリに4時間かかるなら、それは本番インシデントが発生する前に把握しておくべき重要な情報だ。
ロールバックが失敗したり時間がかかりすぎる場合は、必要になる前にプロセスを修正する。このテストはパイプラインの一部とすべきだ。スケジュールされたジョブが毎週ステージングでマイグレーションとロールバックのサイクルを実行し、インフラストラクチャ変更後もリカバリ機構が機能することを確認する。
ロールバック後、再試行前に調査する
ロールバックが成功したら、自然な反応はマイグレーションスクリプトを修正して再実行することだ。その衝動を抑えよ。失敗はより深い問題を明らかにしている可能性がある。マイグレーションが想定していなかったデータの不整合、別のプロセスとの競合状態、スキーマの誤解などだ。
まず根本原因を調査する。マイグレーションログを確認する。失敗の原因となったデータを調べる。マイグレーションが本番環境に存在しないデータ形状を想定していなかったか確認する。なぜ失敗したのかを理解してから、スクリプトを修正し再試行すべきだ。
マイグレーションロールバックの実践的チェックリスト
- パイプラインでマイグレーション前のバックアップステップを自動化する。バックアップが失敗したらパイプラインも失敗させる。
- ダウンマイグレーションは可逆的なスキーマ変更にのみ記述する。破壊的な操作に依存してはならない。
- データベースのポイントインタイムリカバリを設定し、少なくとも四半期に一度テストする。
- 初期設定時だけでなく、本番マイグレーションの前に毎回ステージングでロールバックをテストする。
- ロールバック手順と各環境の推定リストア時間を文書化する。
- ロールバック後、マイグレーションを再試行する前に根本原因を調査する。
具体的な結論
データマイグレーションのロールバックは、実行するスクリプトではない。マイグレーション開始前に構築するシステムである。マイグレーション前のバックアップが第一の防御線だ。ポイントインタイムリカバリが最後の手段である。ダウンマイグレーションは変更が可逆的な狭いケースでのみ有用だ。これらすべてをステージングでテストし、手順を文書化し、ロールバックが機能することを証明するまでは決して機能すると想定してはならない。