一時的な障害の処理
リモートのサービスやリモートのリソースとやり取りするすべてのアプリケーションは、一過性の障害に特別な注意を払う必要があります。 これは、クラウドで実行されるアプリケーションに特に当てはまります。クラウドでは、環境の特性とインターネット経由の接続により、この種の障害がより頻繁に発生する可能性があるからです。 一時的な障害には、コンポーネントやサービスとのネットワーク接続が一瞬失われる、サービスを一時的に利用できなくなる、サービスがビジー状態となってタイムアウトするなどがあります。 多くの場合、これらの障害は自己修正されるため、しばらくしてから操作を繰り返せば、成功する可能性があります。
この記事では、一時的な障害に対処するうえでの一般的なガイダンスを提供します。 Azure サービスを使用している場合の一時的な障害の処理については、「Azure サービスの再試行ガイダンス」を参照してください。
クラウドで一過性の障害が起こる理由
一過性の障害は、プラットフォームやオペレーティング システム、アプリケーションの種類に関係なく、どのような環境でも起こる可能性があります。 ローカルのオンプレミス インフラストラクチャで実行されるソリューションの場合、アプリケーションとそのコンポーネントのパフォーマンスおよび可用性は通常、高価で十分に活用されていないことが多いハードウェアの冗長性によって維持され、コンポーネントとリソースは互互いに近い位置に配置されます。 このアプローチにより障害の可能性は低くなりますが、外部電源やネットワークの問題などの予期しないイベントや、その他の災害シナリオによって停止する可能性があるため、一時的な障害は引き続き発生する可能性があります。
プライベート クラウド システムを含め、クラウド ホスティングには、低コストで入手できる多数のコンピューティング ノードを使った動的リソース割り当て、共有リソース、冗長性、自動フェールオーバーによって、全体的な可用性を引き上げる効果があります。 しかし、クラウド環境の性質上、一時的な障害が発生する可能性はより高くなります。 それには、いくつかの理由があります。
クラウド環境の多くのリソースは共有されており、これらのリソースへのアクセスは、リソースを保護するためにスロットリングの対象となります。 サービスによっては、負荷が特定のレベルにまで上昇するか、最大スループット率に達すると、既存の要求を処理できるだけの余地を確保し、ユーザー全体に対するサービスのパフォーマンスを維持するために接続が拒否されます。 スロットリングによって、共有リソースを使用する近隣および他のテナントに対して高いサービス品質を維持することができます。
クラウド環境では、大量の汎用的なハードウェア ユニットが使用されます。 複数のコンピューティング ユニットとインフラストラクチャ コンポーネントに動的に負荷を分散することで、パフォーマンスを実現します。 故障したユニットを自動的にリサイクルまたは交換することで、信頼性を実現します。 この動的な性質により、一時的な障害や一時的な接続障害が発生する場合があります。
クラウド環境では、アプリケーションとその使用リソースおよびサービスとの間に存在するハードウェア コンポーネント (ルーター、ロード バランサーなどのネットワーク インフラストラクチャを含む) が、そうでない環境と比べて多くなります。 インフラストラクチャが多い分、わずかながらも接続の待機時間や一過性の接続障害が発生します。
クライアントとサーバー間のネットワークの状況が、特にインターネットを経由する通信では変わりやすい傾向があります。 オンプレミス環境であっても、トラフィックが激しく混雑すると、通信速度が低下したり、断続的な接続障害を招いたりすることがあります。
課題
予測しうるあらゆる状況下で十分にテストされていても、一時的な障害がアプリケーションの体感的な稼働率に大きく影響する場合があります。 クラウドでホストされるアプリケーションを安定的に稼働させるためには、次の課題に確実に対応できる必要があります。
アプリケーションは、障害をその発生時に検出し、それらが一時的なものか、長期にわたるものか、または末期的な障害であるのかを判断できることが必要です。 通常、リソースが異なれば障害発生時の対応は異なり、その対応も操作の状況によって異なる可能性があります。 たとえば、記憶域からの読み取り時に発生したエラーへの対応は、書き込み時に発生したエラーへの対応とは異なります。 多くのリソースとサービスには、しっかりと文書化された一時的な障害に関するコントラクトがあります。 しかし、そのような情報が利用できない場合、障害の性質を見極め、それが一時的なものであるかどうかを判断するのが難しい場合があります。
アプリケーションは、障害が一時的なものである可能性が高いと判断した場合、操作を再試行できる必要があります。 また、操作が再試行された回数も追跡する必要があります。
アプリケーションには、再試行に関する適切な戦略が必要です。 その戦略の中で、アプリケーションの再試行の回数、各試行間の延期期間、試行が失敗した後に実行するアクションを指定します。 適切な試行回数と各試行間の延期期間は、決定するのが難しい場合がよくあります。 この戦略は、リソースの種類と、リソースとアプリケーションの現在の動作条件によって異なります。
一般的なガイドライン
次のガイドラインは、アプリケーションに適した一時的な障害処理メカニズムを設計するのに役立ちます。
組み込みの再試行メカニズムがあるかどうかを判断する
多くのサービスには、一過性の障害に対処する機構を持った SDK またはクライアント ライブラリが用意されています。 そこで用いられている再試行ポリシーは通常、対象サービスの性質や要件に合わせて微調整されています。 あるいは、サービスの REST インターフェースによって、再試行が適切かどうか、および次の再試行までの待機時間を判断するのに役立つ情報が返される場合があります。
組み込みの再試行メカニズムが使用可能な場合はそれを使用する必要があります。ただし、十分な理解に基づく具体的な要件があって、別の再試行動作がより適切である場合は例外とします。
操作を再試行するのが妥当かどうかを判断する
再試行操作は、障害が一時的なもの (通常はエラーの性質上) であり、再試行すれば操作に成功する可能性が多少なりともある場合にのみ実行します。 データベースに存在しない項目の更新、致命的なエラーが生じているサービスやリソースへの要求など、無効な操作は再試行しても意味がありません。
一般に、再試行の完全な効果を判断できる場合、および条件が十分に理解され、検証できる場合にのみ、再試行を実装します。 それ以外の場合は、呼び出し元のコードに再試行を実装します。 リソースやサービスから返されるエラーは、利用者側ではどうしようもない問題であり、時間が経つにつれてその姿を変えることもあります。一時的な障害を検出するためのロジックの見直しを迫られる可能性があります。
サービスまたはコンポーネントを作成する際、エラー コードおよびエラー メッセージは、失敗した操作を再試行すべきかどうかをクライアントが判断しやすいように実装してください。 特に、クライアントが操作を再試行すべきかどうかを明らかにし (たとえば isTransient 値を返す)、次の再試行までの適切な延期期間を提案します。 Web サービスを構築する場合は、サービス コントラクト内で定義されているカスタム エラーを返すことを検討してください。 これらのエラーは汎用クライアントで読み取れない場合がありますが、カスタム クライアントの作成には役立ちます。
適切な再試行回数と間隔を決める
ユース ケースの種類に合わせて再試行の回数と間隔を最適化します。 十分な回数再試行しないと、アプリケーションは操作を完了できず、おそらく失敗します。 再試行回数が多すぎるか、試行間隔が短すぎると、スレッドや接続、メモリなどのリソースをアプリケーションが長時間占有し、アプリケーションの正常性に悪影響を及ぼす可能性があります。
時間間隔の値と再試行回数を操作の種類に合わせます。 たとえば、操作がユーザー操作の一部である場合、間隔を短くして、再試行を数回だけに抑える必要があります。 このアプローチを使用すると、ユーザーが応答を待機するのを回避できます。待機している間、接続が開いたままになり、他のユーザーの可用性が低下する可能性があります。 長期実行されるまたは重要なワークフローの中で行われた操作で、処理を取り消したり再起動したりするとコストが高く、時間がかかる場合は、再試行の間隔を長めにし、回数も多めにするのが妥当といえます。
適切な再試行間隔を決めるのは、成功する戦略を練り上げるうえで最も難しい部分であることに注意してください。 一般に、次のような再試行間隔が用いられます。
指数バックオフ。 少し間を置いて 1 回目の再試行を行い、その後、再試行を重ねるたびに加速度的にその間隔を増やしていく手法です。 たとえば、1 回目は 3 秒後、2 回目は 12 秒後、3 回目は 30 秒後というように再試行を行います。
段階的間隔。 少し間を置いて 1 回目の再試行を行い、その後、再試行を重ねるたびに段階的にその間隔を増やしていく手法です。 たとえば、1 回目は 3 秒後、2 回目は 7 秒後、3 回目は 13 秒後というように再試行を行います。
一定間隔。 アプリケーションが待機する試行間隔は一定で変化しません。 たとえば、3 秒おきに操作を再試行します。
即時再試行。 ネットワーク パケットの衝突やハードウェア コンポーネントのスパイクなどのイベントが原因で、一時的な障害が発生することがあります。 その場合、アプリケーションが次の要求を組み立てて送信するまでの間に障害が解消されれば操作に成功する可能性があるので、直ちに操作を再試行するのが妥当です。 ただし即時再試行を複数回にわたって行うことは避けてください。 即時再試行に失敗した場合は、指数バックオフやフォールバック アクションなど、別の戦略に切り替える必要があります。
無作為化。 特定のクライアントの複数のインスタンスが同じタイミングで再試行要求を送信するのを防ぐために、これまでに挙げた再試行戦略にはいずれもランダム化を取り入れることができます。 たとえば、あるインスタンスには 3 秒後、11 秒後、28 秒後、別のインスタンスには 4 秒後、12 秒後、26 秒後に操作を再試行させることができます。 ランダム化は、他の戦略と組み合わせることのできる効果的な手法です。
目安としては、バックグラウンドの操作には指数バックオフを、対話式の操作には即時または一定間隔の再試行を用いるのが一般的です。 どちらの場合も、全再試行を累積した最大待機時間がエンド ツー エンドの待機時間の要件内に収まるように、再試行の間隔と回数を決める必要があります。
再試行操作の最大タイムアウトには、関係するすべての要素を合算して考慮してください。 これらの要因には、失敗した接続が応答を生成するのにかかる時間 (通常、クライアントのタイムアウト値によって設定されます)、再試行間の延期期間、再試行の最大回数が含まれます。 これらすべての時間を合計すると、全体的な操作時間が非常に長くなる場合があります。特に、指数遅延戦略を用いている場合、再試行の間隔が失敗を重ねるごとに急激に増えていくため注意が必要です。 遵守すべき特定のサービス レベル アグリーメント (SLA) がプロセスにある場合、すべてのタイムアウトと待機時間を含め、全体的な操作時間が、SLA で規定されている制限内に収まるようにする必要があります。
過度に積極的な再試行戦略は実装しないでください。 これらは、間隔が短すぎるか、再試行の頻度が高すぎる戦略です。 ターゲット リソースまたはサービスに悪影響を及ぼす可能性があります。 これらの戦略により、リソースやサービスがオーバーロード状態からいつまでも復帰できず、要求をブロック (拒否) し続ける可能性があります。 このシナリオは、ますます多くの要求がリソースまたはサービスに送られるという悪循環に陥ります。 結果的に回復する能力がさらに低下します。
(タイムアウト時間と再試行間隔がほぼ等しい場合など) タイムアウト後すぐに再試行が行われるのを防ぐために、再試行間隔を決めるときは、操作のタイムアウトを考慮してください。 また、想定される時間の合計 (タイムアウト + 再試行間隔) が特定の合計時間未満となるようにする必要があるかどうかも検討してください。 操作のタイムアウトが異常に短いか長い場合、タイムアウトは待機時間と操作の再試行頻度に影響を与える可能性があります。
例外の種類と例外に含まれているデータ、またはサービスから返されるエラー コードとエラー メッセージを使って再試行の回数とその間隔を最適化します。 たとえば、一部の例外またはエラー コード (HTTP コード 503、サービス利用不可とその応答の Retry-After ヘッダーなど) は、どの程度の時間エラーが続くかや、サービスで障害が発生していて再試行しても応答が得られないことを示している場合があります。
アンチパターンを回避する
ほとんどの場合、再試行コードを多層化した実装は避けてください。 階層型の要求を伴う操作において、その各段階で再試行を行う連鎖的な再試行メカニズムを設計に取り入れることは、そうする必要がある特定の要件がない限り、避けてください。 そのような例外的な状況においても、再試行回数と待機時間が度を超えてしまうのを防ぐためのポリシーを設け、想定される結果をきちんと把握しておく必要があります。 たとえば、あるコンポーネントが別のコンポーネントに要求を行い、そのコンポーネントがターゲット サービスにアクセスするとします。 両方の呼び出しで 3 回の再試行を実装すると、サービスに対して合計 9 回の再試行が行われます。 多くのサービスとリソースでは、組み込みの再試行メカニズムが実装されています。 より高いレベルで再試行を実装する必要がある場合は、これらのメカニズムを無効化または変更する方法を調査する必要があります。
再試行が際限なく繰り返される設計は確実に避けてください。 これを行うと、リソースまたはサービスがオーバーロード状態から回復できなくなり、スロットリングが発生したり接続が拒否された状況が長引いたりする可能性があります。 サービスが回復できるように、再試行回数に上限を設けるか、サーキット ブレーカーなどのパターンを実装してください。
即時再試行を複数回にわたって実行しないでください。
Azure 上のサービスやリソースにアクセスするとき、再試行回数が多くなるときは特に、一定間隔での再試行は使用しないでください。 このシナリオでは、指数バックオフにサーキット ブレーカー機能を組み合わせた戦略が最適です。
同じクライアントであれ異なるクライアントであれ複数のインスタンスが同時に再試行要求を送ることのないようにします。 このシナリオが発生する可能性が高い場合は、再試行間隔にランダム化を適用してください。
再試行の戦略と実装をテストする
アプリケーションとそこで使用されるターゲット リソースまたはサービスの両方が極度の負荷にさらされている場合は特に、できるだけ幅広い状況で十分に再試行の戦略をテストしてください。 テスト時の動作確認には、次の方法を用いることができます。
一時的な欠陥と一時的ではない欠陥をサービスに対して注入する。 たとえば、無効な要求を送信することが考えられます。また、テスト要求を検出してさまざまな種類のエラーを返すコードを追加することもできます。 TestApi を使用した例については、TestApi を使った欠陥注入テストと TestApi の概要 (第 5 部: マネージド コードの欠陥注入 API) に関するページを参照してください。
実際のサービスから返される可能性のある一連のエラーを返すリソースまたはサービスのモックアップを作成します。 再試行戦略で検出されるように設計されているすべての種類のエラーをカバーします。
作成してデプロイするカスタム サービスの場合は、サービスを一時的に無効またはオーバーロードすることによって、強制的に一時的なエラーを発生させます。 (Azure で共有リソースや共有サービスをオーバーロードしようとしないでください。)
HTTP ベースの API の場合、自動テストのライブラリを使い、ラウンドトリップ時間を追加するか、応答 (HTTP 状態コード、ヘッダー、本文などの要素) に変更を加えることによって、HTTP 要求の結果に変更を加えます。 そうすることで、一時的な障害やその他の種類の障害について、障害条件のサブセットを決定論的にテストすることができます。
高負荷率と同時実行のテストを実施し、これらの条件下で再試行メカニズムと戦略が正しく機能することを確認します。 これらのテストは、再試行がクライアントの操作に悪影響を及ぼしたり、要求間の相互汚染を引き起こしたりしないようにするのにも役立ちます。
再試行ポリシーの構成を管理する
"再試行ポリシー" は、再試行戦略のすべての要素を組み合わせたものです。 これにより、障害が一時的なものといえるかどうかの基準や使用する間隔の種類 (一定間隔、指数バックオフ、ランダム化など)、実際の試行間隔、再試行の回数などを決定する検出のメカニズムが定義されます。
ごく単純なアプリケーションにも、より複雑なアプリケーションの各レイヤーにも、多くの場所に再試行を実装します。 複数の場所で各ポリシーの要素をハードコーディングするのではなく、中心点を使用してすべてのポリシーを保存することを検討してください。 たとえば、再試行間隔と再試行回数などの値は、アプリケーションの構成ファイルに保存しておき、実行時にそれらを読み取って、プログラムによって再試行ポリシーを構築するようにします。 そうすることで、変化する要件やシナリオに対応するために、設定を管理し、値を変更および微調整することが容易になります。 ただし、構成ファイルを都度読み込むのではなく、一度読み取った値を保存するようにし、構成ファイルから値を取得できなかった場合には適切な既定値を使うようにシステムを設計してください。
Azure Cloud Services アプリケーションでは、実行時点で再試行ポリシーを構築するときに使う値をサービス構成ファイルに保存することをお勧めします。アプリケーションを再起動しなくても、それらの値を変更することができます。
使用するクライアント API で利用可能な組み込みまたは既定の再試行戦略を活用してください。ただし、実際のシナリオに適している場合に限られます。 これらの戦略は通常、一般的なものです。 シナリオによっては、必要なものがすべて揃っている場合もありますが、特定の要件に合ったすべてのオプションが提供されていないシナリオもあります。 最も適切な値を決定するには、テストを実行して、設定がお使いのアプリケーションに与える影響を理解する必要があります。
一時的と一時的ではない障害をログに記録し、追跡する
再試行戦略の一環として、例外処理と、再試行の試行をログに記録するその他のインストルメンテーションを含めます。 時々発生する一時的な障害と再試行は予期されるもので、問題を示すものではありません。 ただし、再試行回数が定期的に増加することは、多くの場合、障害の原因となったり、アプリケーションのパフォーマンスと可用性を低下させたりする問題の兆候です。
一時的な障害をエラー エントリとしてではなく、警告エントリとしてログに記録して、監視システムでそれらがアプリケーション エラーとして検出され、誤ったアラートがトリガーされないようにします。
再試行がサービスのスロットリングによって引き起こされたのか、接続障害などの他の種類の障害によって引き起こされたのかをデータの分析時に区別できるよう、ログ エントリにそれらを示す値を保存することを検討してください。 使用量調整に起因するエラーの件数増加は多くの場合、アプリケーション設計の欠陥や、専用ハードウェアを提供するプレミアム サービスへの切り替えの必要性を示しています。
再試行メカニズムを含む操作の所要時間全体を測定してログに記録することをお勧めします。 このメトリックは、一時的な障害がユーザーの応答時間、プロセスの待ち時間、アプリケーションのユース ケースの効率に与える全体的な影響を示す良い指標です。 また、発生した再試行回数をログに記録して、応答時間に影響する要因を理解できるようにします。
障害の回数や発生率、平均再試行回数、操作が成功するまでの全体の経過時間などが上昇した場合にアラートを発生させることのできるテレメトリと監視システムの導入を検討してください。
継続的に失敗する操作を管理する
試行するたびに失敗し続ける操作を処理する方法を検討してください。 このような状況は避けられません。
再試行戦略は、操作を再試行する最大回数を定義しますが、アプリケーションが同じ再試行回数で操作を繰り返すことを妨げるものではありません。 たとえば、注文処理サービスで致命的なエラーが発生し、完全な機能不全に陥ってしまった場合、再試行戦略によって接続タイムアウトが検出され、それが一時的な障害であると見なされる場合があります。 そのコードは指定された回数、操作を再試行し、その後操作を断念します。 しかし毎回失敗するにもかかわらず、別の顧客から注文があればまた同じ操作が実行されます。
毎回失敗する操作が継続的に再試行されるのを防ぐには、サーキット ブレーカー パターンの実装を検討する必要があります。 このパターンを使用すると、指定された時間枠内に生じた失敗の回数がしきい値を超えた場合、要求はすぐにエラーとして呼び出し元に返され、失敗したリソースまたはサービスへのアクセスは試行されません。
アプリケーションで定期的にサービスをテストしてください。長い間隔で間欠的に要求を送ることによって、稼働状態への復帰を検出できます。 適切な間隔は、操作の重要度やサービスの性質などの要因によって異なります。 数分から数時間の間が適切でしょう。 テストに成功すると、アプリケーションは通常動作を再開し、復旧したサービスに要求を渡すことができます。
その間は、サービスの別のインスタンス (異なるデータセンターやアプリケーション内など) にフォールバックするか、互換性のある (できればよりシンプルな) 機能を持った同様のサービスを使うことが考えられます。または、サービスが間もなく復旧することを期待して応急的な操作を実行することもできます。 たとえば、サービスの要求をキューやデータ ストアに保存しておき、後からそれらを再試行することが適切な場合があります。 または、アプリケーションの代替インスタンスにユーザーをリダイレクトしてもよいでしょう。アプリケーションのパフォーマンスを数段下げつつも許容範囲内の機能を提供するか、アプリケーションが現在利用できないことを示すメッセージをユーザーに返すだけでもかまいません。
その他の考慮事項
再試行回数と再試行間隔に関してポリシーで使用する値を決めるときは、サービスまたはリソースに対する操作が、長時間実行される操作または複数のステップから成る操作の一部であるかどうかを考慮に入れてください。 あるステップが失敗したからといって、既に成功した他の操作ステップをすべてやり直すのは困難であるか、コストがかかりすぎる場合があります。 この場合、その戦略が少ないリソースを占有またはロックして他の操作をブロックしていない限り、再試行間隔や再試行回数がかなり増えるとしても許容範囲です。
同じ操作を再試行すると、データに不整合が生じる可能性があるかどうかを検討してください。 複数ステップのプロセスの一部が繰り返され、その操作にべき等性がない場合、不整合が発生する可能性があります。 たとえば値をインクリメントする操作を繰り返せば、無効な結果が生成されることになります。 メッセージをキューに送信する操作を繰り返すと、コンシューマーが重複するメッセージを検出できなければ、メッセージ コンシューマーで不整合が発生する可能性があります。 こうしたシナリオを防ぐために、それぞれのステップをべき等操作として設計します。 詳細については、「Idempotency patterns」 (べき等性のパターン) を参照してください。
再試行される操作のスコープを考慮に入れてください。 たとえば、再試行コードは複数の操作を包含するレベルで実装し、いずれかの操作に失敗した場合にはすべての操作を再試行する方が簡単な場合があります。 しかしそうすると、べき等性の問題や不要なロールバック操作を招く可能性があります。
複数の操作を含む再試行スコープを選ぶ場合は、再試行間隔を決定するときや操作の経過時間を監視するとき、およびエラーのアラートを生成する前に、それらすべての合計待機時間を考慮に入れてください。
再試行戦略が共有アプリケーションの近隣や他のテナントにどのように影響するか、および共有リソースとサービスをいつ使用するかを検討してください。 極端な再試行ポリシーを用いると、リソースやサービスを共有するアプリケーションおよび他のユーザーに対し一過性の障害の発生頻度を高める可能性があります。 同様に、アプリケーションが、リソースやサービスの他のユーザーによって実装された再試行ポリシーの影響を受ける可能性があります。 ビジネスクリティカルなアプリケーションの場合は、共有されていないプレミアム サービスを使用することをお勧めします。 そうすることで、これらのリソースとサービスの負荷と結果として生じるスロットリングをより細かく制御できるため、追加コストの正当化に役立ちます。