ロールバック:元に戻すことが単純ではない理由
新しいバージョンのアプリケーションをデプロイした直後、5分も経たないうちにモニタリングダッシュボードにエラーが表示され始め、ユーザーから問題の報告が相次ぐ。そんなとき、真っ先に頭に浮かぶのは「古いバージョンに戻す」ことでしょう。これがロールバックの最も単純な形です——最後に安定していた状態に戻し、問題の原因を調査する時間を稼ぐのです。
多くのチームにとって、ロールバックはデフォルトの回復戦略です。直感的にも理解できます。新しいバージョンで問題が起きたのなら、古いバージョンは正常に動いていたのだから、単純に差し替えればよい。しかし現実はもっと複雑です。ロールバックの挙動は、何を元に戻すか——アプリケーションコード、データベーススキーマ、インフラ構成——によって大きく異なります。それぞれに固有の仕組み、リスク、制限があります。
アプリケーションのロールバック:比較的容易なケース
アプリケーションコードのロールバックは、最も単純なシナリオです。現在稼働中のアプリケーションインスタンスに対して、新しいバージョンを古いバージョンに置き換えます。その方法はデプロイ戦略によって異なります。
ブルーグリーンデプロイメントを採用している場合、ロールバックとはトラフィックを古いバージョンが稼働している環境に戻すことを意味します。グリーン環境が再びアクティブになり、ブルー環境はローテーションから外されます。両方の環境がすでに稼働して準備ができているため、この切り替えは数秒で完了します。
カナリアリリースの場合、ロールバックは新しいバージョンへのトラフィックフローを停止し、すべてを以前のバージョンにルーティングし直すことを意味します。カナリアは停止され、安定版がすべてのリクエストを再び処理します。
単純なローリングアップデートの場合、ロールバックは以前のアーティファクトを同じサーバーまたはコンテナに再デプロイすることを意味します。各インスタンスを一つずつ更新する必要があるため時間はかかりますが、プロセス自体は予測可能です。
たとえば、Kubernetesを使用している場合、1つのコマンドでデプロイメントを以前のリビジョンに戻せます。
kubectl rollout undo deployment/my-app -n production
このコマンドは、新しいPodをスケールダウンし、古いPodをスケールアップするようKubernetesに指示し、実質的にローリングアップデートを逆転させます。1ステップ以上戻したい場合は、特定のリビジョンを指定することもできます。
kubectl rollout undo deployment/my-app -n production --to-revision=3
ロールバック前にリビジョン履歴を確認するには:
kubectl rollout history deployment/my-app -n production
アプリケーションロールバックの主な利点は、データを変更しないことです。変更するのは、どのコードが受信リクエストを処理するかだけです。データベースはそのままで、ユーザーデータが失われたり変換されたりすることはありません。このため、アプリケーションのロールバックは比較的安全で高速です。
以下のフローチャートは、このセクションで説明する各ロールバックタイプの判断経路を示しています。
データベースのロールバック:厄介な領域
データベースのロールバックはまったく別の難しさがあります。データベースは継続的に変化する状態を保存します。新しいアプリケーションバージョンがデータベーススキーマを変更した場合——カラムの追加、テーブルのリネーム、データ型の変更など——アプリケーションコードをロールバックするだけでは不十分です。データベース構造も以前の状態に戻さなければなりません。
ここで複雑さが倍増します。単純なシナリオを考えてみましょう。新しいバージョンで users テーブルに phone_number というカラムを追加したとします。アプリケーションはそのカラムに電話番号を書き込み始めます。1時間後、重大なバグを発見してロールバックを決断します。古いアプリケーションコードをデプロイしますが、古いコードは phone_number カラムの存在を知りません。さらに重要なのは、そのカラムにすでに書き込まれたデータをどう扱うかです。削除しますか?別の場所に移動しますか?そのままにして、古いコードが無視してくれることを期待しますか?
最も安全なアプローチは、最初からすべてのデータベースマイグレーションを元に戻せるように設計することです。つまり、各マイグレーションスクリプトには、変更を適用する up ステップと、それを元に戻す down ステップの両方を含めます。ロールバック時には down マイグレーションを実行して、以前のスキーマを復元します。
しかし、すべての変更がデータ損失なしに本当に元に戻せるわけではありません。新しいバージョンでまだ使用中のカラムを削除してしまった場合、ロールバックではそのカラムを再作成し、バックアップからデータを復元する必要があります。新しいバージョンで2つのテーブルを1つに統合した場合、ロールバックではそれらを分割し、どの行がどのテーブルに属していたかを特定しなければなりません。これらの操作はリスクが高く、時間がかかり、多くの場合手動での介入が必要です。
多くのチームはこの現実を受け入れ、データベースのロールバックを完全に避けることを選択します。その代わり、デプロイ前のマイグレーションテストに多大な投資をし、本番データを可能な限り忠実にミラーリングしたステージング環境でテストを実施します。何か問題が発生した場合でも、後方へのロールバックを試みるのではなく、前方修正(フォワードフィックス)を書くことを優先します。
インフラのロールバック:隠れた依存関係の網
インフラのロールバックとは、サーバー、ネットワークルール、ロードバランサー、またはサポートサービスの変更を元に戻すことを意味します。Terraform、Ansible、Pulumiなどのツールでインフラを管理している場合、ロールバックは通常、設定ファイルの以前のバージョンを適用することを意味します。
ここでの課題は、インフラの変更が単一のものに影響を与えることはほとんどないという点です。ファイアウォールルールの変更がデータベース接続を壊すかもしれません。ロードバランサーの設定変更が複数のサービスのトラフィックルーティングに影響を与えるかもしれません。Terraformの状態ファイルをロールバックすると、新しいバージョンで作成されたリソースが削除され、それが他の問題に連鎖する可能性があります。
インフラのロールバックには時間もかかります。以前の設定を適用するには、最初にインフラを作成したときと同じプロビジョニングプロセスを実行する必要があります。インフラが大規模または複雑な場合、これには数分から数時間かかる可能性があります——その間、ユーザーはエラーを経験し続けることになります。
戦略としてのロールバックの限界
ロールバックは万能のセーフティネットではありません。次の3つの条件がすべて満たされた場合にのみ機能します。
第一に、古いバージョンが現在のシステム状態と互換性があり、安定している必要があります。新しいバージョンが数時間稼働し、ユーザーが古いバージョンでは読み取れないデータを入力した場合、ロールバックはデータ損失や破損を引き起こします。
第二に、問題がコードや設定にあり、データ自体にあるのではないこと。ユーザーが機能を誤用している、またはデータ品質が低下しているという問題の場合、コードをロールバックしても何も解決しません。
第三に、ロールバックにかかる時間が、前方修正(フォワードフィックス)を書く時間よりも短いこと。ロールバックに30分かかり、ホットフィックスを書くのに10分しかかからないのであれば、ロールバックはより遅い選択肢です。
行動面でのリスクもあります。ロールバックに過度に依存するチームは、デプロイ前のテストがおろそかになる可能性があります。「壊れたらロールバックすればいい」という考え方です。これは危険です。ロールバックには実際のコストが伴います。エラーを目にしたりデータを失ったりしたユーザーは、5分で復旧したことを気にしません。彼らの信頼はすでに損なわれているのです。
ロールバックを決断する前の実践的チェックリスト
ロールバックを実行する前に、次の質問を自問してください。
- 古いバージョンはまだ稼働しており、トラフィックを受け入れる準備ができているか?
- データベーススキーマが、古いコードと互換性がなくなる形で変更されたか?
- 新しいバージョンはどのくらいの期間稼働しており、どの程度のユーザーデータが入力されたか?
- データベースマイグレーションはデータ損失なしに元に戻せるか?
- ロールバックは前方修正を書くよりも速いか?
- ロールバック計画をチームとステークホルダーに伝達したか?
これらの質問のいずれかで危険信号が上がった場合は、代わりにロールフォワード(前方修正)を検討してください。
まとめ
ロールバックは正当な回復戦略ですが、無料の「元に戻す」ボタンではありません。アプリケーションのロールバックは比較的安全で高速です。データベースのロールバックはリスクが高く、しばしば不可逆的です。インフラのロールバックは遅く、連鎖的な影響を及ぼす可能性があります。ロールバックをデフォルトの対応とする前に、何を元に戻そうとしているのか、そして実際のコストが何であるのかを理解してください。時には、前方修正(フォワードフィックス)の方が良い選択肢です——それについては次に見ていきます。