自己復旧の設計
障害の発生に備えてアプリケーションの自己復旧を設計する
分散システムでは、障害の発生を想定する必要があります。 ハードウェアの障害が発生することがあります。 ネットワークに一時的な障害が起きることがあります。 サービス全体、データ センター全体、または Azure リージョン全体で中断が発生することはほとんどありませんが、これらもワークロード アーキテクチャに応じて設計する必要があります。 回復性と復旧については、ワークロード設計の早い段階で対処する必要があります。
そこで、障害の発生に備えて、自己復旧するようにアプリケーションを設計します。 これには、3 つのアプローチが必要です。
- 障害を検出する。
- 障害に適切に対応する。
- 障害をログ記録および監視し、Operational Insightsを提供する。
特定の種類の障害にどのように対応するかは、アプリケーションの可用性要件によって異なります。 たとえば、高可用性が必要な場合は、リージョン内の複数の可用性ゾーンにデプロイできます。 Azure リージョン全体で中断が発生するという万が一の事態でも停止を回避するために、リージョンの停止中にセカンダリ リージョンに自動的にフェールオーバーできます。 ただし、単一リージョンのデプロイよりもコストが高くなり、パフォーマンスが低下する可能性があります。
また、リージョン障害などの大規模な事象は通常まれなので、それだけを考慮するのではいけません。 それ以上ではないにしてもそれと同等程度に、ネットワーク接続障害やデータベース接続障害といったローカルで短期間の障害の処理を、重視する必要があります。
推奨事項
非同期で通信する分離コンポーネントを使用する。 理想的には、コンポーネントは時間と空間の観点から分離されます。 時間で切り離されたということは、通信を可能にするためにコンポーネントが同時に存在する必要がないようにすることを意味します。 スペースで切り離された場合、送信者と受信者は同じプロセスで実行する必要はありませんが、より効率的な場所であればどこでも実行できます。 理想としては、切り離されたコンポーネント間のやり取りにはイベントを使用する必要があります。 これにより、連鎖的な障害が発生する可能性を最小限に抑えることができます。
失敗した操作を再試行する。 一時的な障害は、ネットワーク接続の一時的な喪失、データベース接続の切断、またはサービスがビジー状態のときのタイムアウトにより発生する可能性があります。 アプリケーションに再試行ロジックを構築して、一時的な障害を処理します。 多くの Azure サービスでは、クライアント SDK が自動再試行を実装します。 詳細については、「一時的な障害の処理」および「再試行パターン」を参照してください。
リモート サービスを保護する (サーキット ブレーカー) 。 一時的な障害の後に再試行するのはよいのですが、障害が解決しない場合には、障害が起きているサービスにアクセスする呼び出し元が多すぎる、ということになりかねません。 要求はバックアップされるため、これでは障害が山積みになっていくことになります。 操作が失敗しそうなときに (リモート呼び出しを行わずに) フェイル ファストするには、 サーキット ブレーカー パターン を使用します。
重要なリソースを分離する (バルクヘッド) 。 1 つのサブシステムで障害が連鎖することがあります。 これが起きるのは、障害によってスレッドやソケットなどのリソースが適切なタイミングで解放されない場合で、リソースの消費につながります。 これを回避するには、 バルクヘッド パターン を使用して、システムを分離グループにパーティション分割して、1 つのパーティションでの障害がシステム全体をダウンさせないようにします。
負荷平準化を行う。 アプリケーションでは突然のトラフィックの急増が発生し、バックエンドのサービスに負担がかかる可能性があります。 これを回避するには、 キュー ベースの負荷平準化パターン を使用して、作業項目をキューに入れて非同期的に実行します。 キューは、負荷のピークを平準化するバッファーとして機能します。
フェールオーバーする。 インスタンスに到達できない場合は、別のインスタンスにフェールオーバーします。 Web サーバーのようなステートレスなものについては、複数のインスタンスを、ロード バランサーまたはトラフィック マネージャーの背後に配置します。 データベースのように状態を保持するものについては、レプリカを使用してフェールオーバーします。 データ ストアとそのレプリケート方法によっては、最終的な整合性に対処するアプリケーションが必要なことがあります。
失敗したトランザクションを補正する。 一般的に、分散トランザクションは避けてください。サービスとリソースの間で調整が必要になるからです。 代わりに、より小さな個別のトランザクションで 1 つの操作を構成します。 操作が途中で失敗した場合は、 補正トランザクション を使用して、既に完了した手順をすべて元に戻します。
実行時間の長いトランザクションにチェックポイントを設ける。 実行時間の長い操作が失敗した場合には、チェックポイントで回復性を実現できます。 操作が再起動する (たとえば、別の VM で取得される) ときに、最後のチェックポイントから再開できます。 一定の間隔でタスクの状態に関する情報を記録するチェックポイント メカニズムを実装し、タスクを実行しているプロセスのすべてのインスタンスがアクセスできる永続的ストレージにその状態を保存することを検討してください。 このようにすることで、プロセスがシャットダウンされた場合に、別のインスタンスを使用して、実行されていた作業を最後のチェックポイントから再開できます。 NServiceBus や MassTransit など、この機能を備えたライブラリがあります。 これにより、間隔が Azure Service Bus 内のキューからのメッセージの処理と一致する状態が透過的に保持されます。
障害時には適切に機能を低下させ、応答性を維持する。 ときには問題を処理できないこともありますが、制限された内容でもなお役立つような機能を提供することは可能です。 書籍のカタログを表示するアプリケーションを例に説明します。 そのアプリケーションが表紙のサムネイル画像を取得できない場合は、プレース ホルダー イメージを表示することができます。 サブシステム全体が、アプリケーションにとって重要でない場合もあります。 たとえば電子商取引サイトでは、製品レコメンデーションの表示は、注文の処理よりもおそらく重要度が低いでしょう。
クライアントを制限する。 少数のユーザーによって過度の負荷が発生することがありますが、これにより他のユーザーに対するアプリケーションの可用性が低下することがあります。 このような状況では、一定の時間、そのクライアントを制限します。 「調整パターン」を参照してください。
問題のあるユーザーをブロックする。 クライアントを制限するからと言って、そのクライアントが悪意を持って操作していたことにはなりません。 そのクライアントがサービスのクォータを超えたというだけです。 しかしクライアントが一貫してクォータを超えるまたは不適切な動作をする場合は、それをブロックすることもあります。 ユーザーがブロック解除を要求できるように、定型外のプロセスを定義しておきます。
リーダー選択を使用する。 タスクを調整する必要がある場合は、 リーダー選択 を使用してコーディネーターを選択します。 そうすると、コーディネーターは単一障害点ではありません。 コーディネーターが失敗すると、新しいコーディネーターが選択されます。 最初からリーダー選択アルゴリズムを実装するよりも、Zookeeper などの既製のソリューションを検討してください。
フォールト挿入でテストする。 ほとんどの場合、成功のパスは十分にテストされますが、障害のパスはそうではありません。 システムが実稼働環境で長期間にわたり実行されてから初めて、障害のパスが実行されることもあります。 フォールト挿入を使用して、実際のエラーをトリガーするまたはシミュレートすることによって、システムの障害への回復性をテストすることができます。
Chaos エンジニアリングを利用する。 Chaos エンジニアリングは、実稼働インスタンスに障害や異常な状態をランダムに挿入することで、フォールト挿入の概念を拡張するものです。
可用性ゾーンを使用する。 多くの Azure リージョンは、リージョン内の分離されたデータ センターのセットである 可用性ゾーンを提供します。 一部の Azure サービスは ゾーン単位にデプロイでき、これによりサービスが確実に特定のゾーンに配置され、同じワークロード内のコンポーネント間の通信の待機時間を短縮できます。 あるいは、一部のサービスは ゾーン冗長を使用してデプロイできます。これは、高可用性を実現するために、Azure がゾーン間でリソースを自動的にレプリケートすることを意味します。 どのアプローチがソリューションにとって最適なトレードオフを提供するかを検討してください。 可用性ゾーンとリージョンを使用するようにソリューションを設計する方法の詳細については、 「可用性ゾーンとリージョンを使用 するための推奨事項」を参照してください。
アプリケーションを自己復旧させるための構造化されたアプローチについては、 Azure のための信頼性の高いアプリケーションの設計に関するページを参照してください。