Blue-Green とカナリア デプロイ:API とデータベースの変更を安全に
API とデータベースの変更における Blue-Green とカナリア デプロイを解説し、スキーママイグレーションや更新の遅いモバイルクライアントがある場合のダウンタイムリスクを減らす実践的な手順を示します。

スキーマ変更と更新の遅いモバイルでデプロイが危険になる理由
本番トラフィックに当たると、テストでは完璧に見えたデプロイが突然失敗することがあります。多くの場合の原因は、変わるのはコードだけではないということです。API の契約やデータベースのスキーマも変化しており、これらは同じ速度で変わるとは限りません。
システムの各部が「正しい」状態について合意していないと問題が起きます。新しいバックエンドがまだ存在しない列を期待する。古いバックエンドが新しいコードで理解できない形式でデータを書き込む。フィールド名の変更、バリデーションの厳格化、列挙値の変更といった小さな変化でも本番エラーにつながります。
モバイルアプリは状況をさらに厳しくします。古いバージョンが長く残るため、ユーザーの中には数分で更新する人もいれば数週間かかる人もいます。つまりバックエンドは複数世代のクライアントを同時にサービスし続けなければなりません。最新アプリでしか動かない API を出すと、チェックアウトやオンボーディング、バックグラウンド同期が一部のユーザーで壊れてもすぐには気づかないことがあります。
「ダウンタイムリスク」はサイトが完全に落ちることだけではありません。実際のシステムでは部分的な障害として現れることが多いです:
- 他は問題なさそうなのに特定のエンドポイントで 4xx/5xx が急増する
- トークンやロール、ユーザーレコードが期待と合わずサインインに失敗する
- 間違ったデフォルト、切り詰められたテキスト、欠けたリレーションなどの静かなデータ問題が数日後に表面化する
- バックグラウンドジョブが詰まってキューが何時間も溜まる
だからこそチームは Blue-Green と Canary の比較をするわけです:互換性が完璧でない変更の被害範囲を減らしたいからです。
Blue-Green と Canary をわかりやすく説明すると
Blue-Green と Canary の比較は、たいてい次の問いに答えるために行われます:大きくコントロールされた切り替えをするか、小さく慎重に試すか、どちらにしますか?
Blue-Green:二つの完全な環境とトラフィックの切り替え
Blue-Green では二つの完全な環境を並行して動かします。「Blue」は現在ユーザーにサービスしているバージョン。「Green」は新しいバージョンで、並行してデプロイしてテストします。準備ができたら Blue から Green にトラフィックを切り替えます。
この方法は予測可能性に優れています。本番に近い設定で新バージョンを検証してから一度に切り替えられるので、ロールバックも単純です:問題があればトラフィックを Blue に戻すだけです。ただしキャッシュやバックグラウンドジョブ、データの変更が回復を複雑にすることはあります。
Canary:まずは少数のトラフィックだけ送る
Canary では新バージョンをまず少数のユーザーやリクエストにだけ公開し、正常なら段階的にその割合を増やします。
Canary は本番トラフィックでの未知の挙動が心配なときに有効です。問題を大多数のユーザーが感じる前に検出できます。
ロールバックは通常はその割合をゼロに戻すか新バージョンへのルーティングを止めることで行います。速いことが多いですが、既に一部のユーザーがデータや状態を作成しているとクリーンにならないこともあります。
選択のトレードオフを簡単にまとめると:
- Blue-Green はクリーンな切り替えと迅速な復帰を優先します。
- Canary は実際のトラフィックから学びながら被害範囲を限定します。
- どちらもデータベースのリスクを自動で解決するわけではありません。スキーマ変更が互換でないと失敗します。
- Canary はライブのシグナルに基づいて判断するため監視に依存します。
- Blue-Green は二つの完全なスタックを動かすため追加のキャパシティが必要になることが多いです。
例:API が新しいフィールドを返す場合、Canary は古いクライアントが予期しないデータでクラッシュするかを見るのに役立ちます。もし変更が列名のリネームで古いコードが対応できないなら、Blue-Green でも救えないでしょう。スキーマ自体が両バージョンをサポートするよう設計されている必要があります。
なぜデータベースのマイグレーションはコードデプロイと違うのか
コードのデプロイは通常ロールバックが簡単です。新しいバージョンが問題を起こしたら以前のビルドを再デプロイすれば大抵は元に戻ります。
データベースの変更はデータの形を変えるため違います。行が書き換えられ、列が削除され、制約が厳しくなると元に戻すのが瞬時にはできません。アプリコードをロールバックしても新しいスキーマを理解できない場合があります。
だからスキーママイグレーションのダウンタイムリスクは、デプロイ手法そのものよりマイグレーションの設計に依存することが多いのです。
オンラインマイグレーションの基本
最も安全なマイグレーションは、古いアプリと新しいアプリが同時に動けるように作られています。パターンは簡単です:無視しても安全な変更を行い、コードを更新して使い、後でクリーンアップします。
一般的な「拡張してから収縮する」シーケンスは次の通りです:
- まず付加的な変更:nullable の列を追加する、新しいテーブルを追加する、書き込みをロックしない方法でインデックスを追加する。
- 両対応の振る舞い:古い方と新しい方の両方に書く、または新しい方を読みつつ古い方へフォールバックする。
- バックフィルを別で実行:既存データを小さなバッチで移行する。
- 切り替え:新しい振る舞いにトラフィックを移す。
- 破壊的変更は最後:古い列を削除し、古いコードパスを除去し、制約を厳しくする。
「一発で全部やる」マイグレーションは最もリスクが高く、長いロックや重いバックフィル、どこでも新スキーマを前提にしたコードを組み合わせてしまいがちです。
更新の遅いモバイルがハードルを上げる理由
モバイルクライアントは古いバージョンのまま数週間残ることがあります。バックエンドは古いリクエストを受け取り、古いレスポンスを返し続けながらデータベースを進化させる必要があります。
古いアプリが新しいフィールドを送らない場合、サーバーがそのフィールドをデータベースで必須にすることはできません。両方の振る舞いが動く期間が必要です。
スキーママイグレーションのダウンタイムリスクを減らすのはどちらか
安全な選択は、デプロイツールよりも「古いと新しいアプリが一定期間同じスキーマで正しく動けるか?」という問いに依存します。
もし両方が同じスキーマで動けるなら、Blue-Green はダウンタイムが最も少なくなることが多いです。データベース変更を先に準備し、トラフィックは旧スタックに残したままグリーンをテストしてから一度に切り替えられます。問題があれば素早く切り戻せます。
ただし新しいアプリが新スキーマを即時に必要とする場合は Blue-Green でも失敗します。よくある例は、古いバージョンがまだ参照している列を削除したりリネームしたりする場合や、アプリが値を書き込む前に NOT NULL を追加する場合です。その場合、ロールバックは安全でないことがあります。
Canary は本番での学習が必要なときに向いています。少数のトラフィックで新バージョンを試すことで、欠けたインデックス、予期しないクエリパターン、バックグラウンドジョブの違いなどのエッジケースを早めに検出できます。トレードオフは、両バージョンを同時に動かしておく必要があり、通常は後方互換なデータベース変更を前提とする点です。
実用的な判断ルール
Blue-Green と Canary をスキーママイグレーションのダウンタイムリスクで比べるとき:
- スキーマ変更が付加的で互換性を保てるなら、クリーンな切り替えを狙って Blue-Green を選ぶ。
- 本番での挙動に不安がある、または稀なデータ形が影響しそうなら Canary を選ぶ。
- マイグレーションが即座に壊れる変更を強いるなら、Blue-Green と Canary のどちらかを選ぶ前に計画自体を「拡張してから収縮する」方式に変える。
実際の互換性の例
orders テーブルに新しいフィールドを追加する場合、安全な道筋は:列を nullable として追加し、アプリをデプロイして書き込むようにしてから既存行をバックフィルし、後で制約を厳しくする、という流れです。この構成では Blue-Green はクリーンなカットオーバーを提供し、Canary は古いコードパスがまだ存在するかどうかの早期警告を出します。
更新の遅いモバイルがデプロイ選択をどう変えるか
Web ユーザーはページをリロードしますが、モバイルユーザーはしません。
iOS や Android では人々が古いバージョンを数週間〜数ヶ月使い続けることがあり、中には強制アップデートまで更新しない人もいます。つまり「古い」クライアントが新しいバックエンドを呼ぶことが長期間続きます。古いモバイルクライアントは、永久に残る互換性のテストのようなものになります。
このため目標は「ダウンタイムゼロでデプロイする」から「複数世代のクライアントを同時に動かし続ける」へと変わります。実務では、インフラは Blue-Green を使っても API 設計は Canary 的な考え方を採ることが多くなります。
後方互換な変更と API バージョニング
多くの場合、後方互換な変更が望まれます。古いクライアントと新しいクライアントが同じエンドポイントを共有できるためです。
後方互換の例:新しいフィールドの追加、古い/新しいペイロード形状の両対応、既存レスポンスフィールドの維持、意味の変更を避けること。
行動そのものを変える必要がある、あるいはフィールドを削除・リネームする必要があるときは、モバイル向けの API バージョニングが有効になります。
例:marketing_opt_in のようなオプションフィールドを追加するのは通常安全です。price の計算方法を変えるのは安全ではありません。
廃止(deprecation)期間の計画
破壊的な変更が必要な場合、サポート終了は製品上の判断として扱ってください。便利な廃止ウィンドウは「古いバージョンをまだ使っているアクティブユーザーの割合」で測ります。
実用的な順序:
- 古いと新しい両方のクライアントをサポートするバックエンドを先に出す。
- 新しいモバイルアプリをリリースし、バージョンごとの採用状況を追う。
- 古いバージョンが安全な閾値を下回ったら警告や制限をかける。
- 古い振る舞いは最後に削除し、ロールバック計画を用意する。
ステップバイステップ:API とデータベースを同時に変更する安全なロールアウト
API とデータベースを同時に変えるとき、最も安全な計画は通常 2〜3 段階のロールアウトです。各ステップは単独でデプロイしても安全であるべきで、古いモバイルアプリが数週間残っていても耐えられることが必要です。
古いクライアントを壊さないロールアウトパターン
まず付加的なデータベース変更を行います。新しい列やテーブルを追加し、リネームや削除は避け、必要に応じて NULL を許容し、古いコードが突然制約に引っかからないようデフォルトを使います。
次にアプリコードをデプロイして両方のデータ形状に耐えるようにします。読み取りは「新しいフィールドがなければ古い方を使う」、書き込みは当面は古いフィールドを書き続けつつ新しいフィールドにも書く、という形にします。
典型的なシーケンス:
- 古いものを削除せずに新スキーマ要素(列、テーブル、インデックス)を追加する。
- 古い/新しい両方を読めるコードをデプロイし、null によるクラッシュを防ぐ。
- 既存行を小さなバッチでバックフィルし、件数、null の割合、クエリ性能を検証する。
- 書き込みパスを新フィールドに切り替え、フォールバックは残す。
- 古いモバイルバージョンが減ってから古いフィールドや古いコードを削除する。
バックフィルと検証:ここに障害が隠れる
バックフィルは「ちょっとしたスクリプト」として扱うと失敗します。段階的に実行し、負荷を見ながら結果を検証してください。新しい振る舞いがインデックスを必要とするなら、読み/書きを切り替える前にそのインデックスを追加します。
例:phone_country_code を追加してフォーマットを改善する場合、まず列を nullable として追加し、API を更新して存在しなくても動くようにし、既存電話番号からバックフィルし、最後に新規登録で書き込むようにします。数週間後に古いパース経路を削除できます。
両方の戦略を安全にするツール(大げさにしない方法)
複雑なセットアップがなくても、いくつかの習慣で API とスキーマのズレによる驚きを減らせます。
一時的な Dual-read / Dual-write
Dual-write は移行期間中に古い場所と新しい場所の両方に書き込みます。Dual-read は通常新しいフィールドを優先して読み、なければ古い方へフォールバックします。
これは遅いクライアントの更新に猶予を与えますが短期間の橋渡しにすべきです。どの経路が使われているかを追跡し、一貫性チェックを入れ、いつ削除するかを決めておきます。
挙動変更に対するフィーチャーフラグ
フィーチャーフラグがあれば危険な挙動をオフにしたままコードをデプロイでき、後から段階的にオンにできます。これにより「デプロイ」と「有効化」を分離でき、Blue-Green と Canary の両方で役に立ちます。
契約テストの考え方(API は約束)
多くのマイグレーション事故は実際には「データベースの問題」ではなくクライアントの期待の問題です。
API を約束事として扱い、フィールドを削除したり意味を変えたりすることを避けます。未知のフィールドはオプションにし、付加的な変更(新フィールドや新エンドポイント)を優先し、破壊的変更は API の新バージョンに待たせます。
信頼できるデータ移行ジョブ
スキーマ移行にはバックフィルジョブが必要になることが多いですが、これらは再実行可能でリトライでき、負荷を起こさないようスロットリングされているべきです。退屈で再現可能であるほど安全です。
マイグレーション中の障害を招くよくあるミス
ほとんどの障害は、すべてが同時に動くことを前提にリリースを組んでしまうことから来ます:すべてのサービスが一斉にデプロイされ、すべてのデータがクリーンで、すべてのクライアントがすぐ更新される、という想定です。モバイルが絡むとこれは現実的ではありません。
よくある失敗パターン:
- 列を早すぎて削除/リネームしてしまう。古い API コードやバックグラウンドジョブ、古いモバイルがまだ使っている可能性がある。
- クライアントが早く更新することを前提にする。モバイルのアップデートには時間がかかる。
- ピーク時間に大きなテーブルをロックするマイグレーションを実行する。単純なインデックス追加でも書き込みをブロックすることがある。
- クリーンなサンプルデータだけでテストする。本番データには null、奇妙なフォーマット、重複、レガシー値が含まれる。
- コードとデータ両方の本当のロールバック計画がない。「前のバージョンを再デプロイすればいい」は、スキーマが変わっていると不十分。
例:status を order_status にリネームして新 API をデプロイした。ウェブは動くが古いモバイルクライアントは status を送っており、API がそれを拒否してチェックアウトが失敗する。列を削除してしまっていたら復元はすぐにはできません。
より良いデフォルトは:小さく可逆なステップで変更する、古いパスと新しいパスを共存させる、メトリクスが急増したときに何をするかを書き出しておく(トラフィックの戻し方、オフにするフィーチャーフラグ、バックフィルが失敗したときの検証と修復手順)ことです。
デプロイ直前の簡単チェックリスト
リリース直前に短いチェックリストを回すことで、夜中のロールバックにつながる問題を検出できます。API とデータベースを同時に変更するとき、特にモバイルの更新が遅い場合に重要です。
障害を防ぐ五つのチェック
- 互換性: 古いアプリと新しいアプリの両方が同じスキーマで動作することを確認する。実用的なテストは、現在の本番ビルドを新しいマイグレーションが適用されたステージング DB で動かしてみること。
- マイグレーション順: 最初のマイグレーションは付加的にし、破壊的な変更(列削除、制約の厳格化)は後に回す。
- ロールバック: 最速の取り消し方法を定義する。Blue-Green はトラフィックの切り戻し、Canary は安定版へ 100% 戻すことが多い。ロールバックに別のマイグレーションが必要なら簡単ではない。
- 性能: 正しさだけでなくスキーマ変更後のクエリ遅延を測る。インデックスがないとあるエンドポイントが事実上落ちたように見えることがある。
- クライアントの現実: まだ API を呼んでいる最古のモバイルアプリバージョンを特定する。意味のある割合が残っているなら互換ウィンドウを長く取る。
簡単な正気のシナリオ
preferred_language のような新しいフィールドを追加するなら、まず DB を nullable で変更する。次にそれを参照しても必須にしないサーバーコードを出してから、更新が進んだら必須にする、という順序が安全です。
例:古いモバイルアプリを壊さずに新しいフィールドを追加する
プロフィールに country を追加して必須にしたいとします。問題が起きるのは二箇所:古いクライアントが送らない、データベースが早期に書き込みを拒否する、です。
安全な方法は二段階です:まず互換性のある形でフィールドを追加し、後で「必須」にします。
Blue-Green でのやり方
Blue-Green では新旧を並行してデプロイしますが、スキーマ変更が両方に対応している必要があります。
安全な流れ:
- マイグレーションをデプロイ(
countryを nullable として追加) countryがなくても動く green バージョンをデプロイしてフォールバックを用意する- signup、プロファイル編集、チェックアウトなど主要フローを green でテストする
- トラフィックを切り替える
問題があれば切り戻せます。ただし切り戻しが機能するのはスキーマが古いバージョンをまだサポートしている場合だけです。
Canary でのやり方
Canary ではまず 1%〜5% など少数のトラフィックに新 API を当てて、必須フィールドのバリデーションエラーやレイテンシ、予期しない DB エラーを監視します。
よくある驚きは古いモバイルクライアントが country なしでプロフィール更新を送ってくることです。API が即座に必須にすると 400 エラーが出ますし、DB が NOT NULL を強制していると 500 エラーになることもあります。
安全な順序:
countryを nullable として追加(必要なら安全なデフォルト"unknown"を使う)- 古いクライアントからの
country欠落を受け入れる - バックグラウンドジョブで既存ユーザーを少しずつバックフィルする
- 採用が進んだら API で先に必須にし、その後データベース制約を厳しくする
リリース後は古いクライアントが何を送れるか、サーバーが何を保証するかを文書化しておくと、次のマイグレーションで同じ破綻を防げます。
もし AppMaster (appmaster.io) のようなプラットフォームで開発しているなら、同じロールアウトの規律を使ってください。モデルからバックエンド、ウェブ、ネイティブアプリを生成できても、まず付加的なスキーマ変更と寛容な API ロジックを出して、採用が進んだら制約を厳しくするという流れは変わりません。
よくある質問
Blue-Green は二つの完全な環境を用意して一度に切り替えます。Canary はまず少数のユーザーに新しいバージョンを公開し、実際のトラフィックで様子を見て段階的に増やします。
Blue-Green はクリーンなカットオーバーを行いたいときに有効です。新バージョンが現在のデータベーススキーマと互換性があると確信している場合に選びます。主なリスクがアプリケーションコード側にあるときに特に役立ちます。
Canary は本番トラフィックで挙動を学びたいときに安全です。クエリパターンや稀なデータ、バックグラウンドジョブが本番で異なる挙動をする可能性がある場合に使います。被害範囲を小さくできますが、メトリクス監視とロールバック準備が必要です。
いいえ。スキーマ変更が互換性を壊す場合(列を削除/リネームするなど)、Blue-Green も Canary も失敗する可能性があります。安全なのは、古いバージョンと新しいバージョンが同時に動けるオンラインマイグレーション設計です。
モバイルユーザーは古いバージョンのまま長期間残ることが多く、バックエンドは複数世代のクライアントを同時にサポートし続けなければなりません。これがデプロイリスクを高めます。
まずは既存コードが無視できる付加的な変更(nullable の列や新しいテーブルなど)から始めます。次に両方の形を許容するコードをデプロイしてバックフィルを段階的に行い、動作が安定したら古いフィールドを削除したり制約を厳しくします。
古いクライアントが送っているものと期待しているレスポンスを洗い出し、フィールドの削除や意味の変更を避けます。新しいオプションフィールドを追加し、古い/新しいリクエスト形状の両方を受け入れ、「必須」にするのは採用状況が十分になってからにします。
Dual-write は移行期間中に古い場所と新しい場所の両方に書き込み、Dual-read は新しいフィールドを優先して読み、なければ古い方にフォールバックすることです。短期間のブリッジとして使い、どの経路が使われているかを追跡し、明確なクリーンアップ計画を立てておきます。
フィーチャーフラグはリスクのある挙動をオフのままコードをデプロイし、後から段階的にオンにできるようにします。問題が出たらフラグをオフにするだけで済むため、Blue-Green の切替や Canary の段階的導入で有用です。
よくある失敗は、列を早く削除/リネームすること、クライアントの送信を待たずに NOT NULL を強制すること、ピーク時にロックを伴うマイグレーションを走らせることです。また、テストデータと本番データの違いを過小評価してバックフィルや検証が失敗することも多いです。


