リモート サービスとリソースと通信するすべてのアプリケーションは、一時的な障害の影響を受ける必要があります。 これは特に、クラウドで実行されるアプリケーションに当てはまります。環境の性質とインターネット経由の接続により、この種の障害が頻繁に発生する可能性が高くなります。 一時的な障害には、コンポーネントとサービスへのネットワーク接続の一時的な損失、サービスの一時的な使用不能、サービスがビジー状態のときに発生するタイムアウトなどがあります。 多くの場合、これらのエラーは自己修正されるため、適切な遅延の後にアクションが繰り返されると、成功する可能性があります。
この記事では、一時的な障害処理に関する一般的なガイダンスを提供します。
クラウドで一時的な障害が発生するのはなぜですか?
一時的な障害は、任意の環境、任意のプラットフォームまたはオペレーティング システム、および任意の種類のアプリケーションで発生する可能性があります。 ローカルのオンプレミス インフラストラクチャで実行されるソリューションの場合、アプリケーションとそのコンポーネントのパフォーマンスと可用性は、通常、コストが高く、頻繁に使用されていないハードウェア冗長性によって維持され、コンポーネントとリソースは互いに近接して配置されます。 この方法では、障害の可能性は低くなりますが、外部電源やネットワークの問題などの予期しないイベントや、その他の障害シナリオによって引き起こされる障害など、一時的な障害が引き続き発生する可能性があります。
プライベート クラウド システムを含むクラウド ホスティングでは、共有リソース、冗長性、自動フェールオーバー、および多くのコモディティ コンピューティング ノード間での動的リソース割り当てを使用することで、全体的な可用性を高めることができます。 ただし、クラウド環境の性質上、一時的な障害が発生する可能性が高くなります。 これにはいくつかの理由があります。
クラウド環境内の多くのリソースは共有されており、これらのリソースへのアクセスは、リソースを保護するために調整の対象となります。 一部のサービスは、負荷が特定のレベルに達したとき、または最大スループット レートに達したときに接続を拒否し、既存の要求の処理を許可し、すべてのユーザーのサービスのパフォーマンスを維持します。 調整は、共有リソースを使用する近隣テナントや他のテナントのサービス品質を維持するのに役立ちます。
クラウド環境では、大量のコモディティ ハードウェア ユニットが使用されます。 複数のコンピューティング ユニットとインフラストラクチャ コンポーネントに負荷を動的に分散することで、パフォーマンスを実現します。 故障したユニットを自動的にリサイクルまたは交換することで、信頼性を実現します。 このような動的な性質のため、一時的な障害や一時的な接続エラーが発生することがあります。
多くの場合、アプリケーションと使用するリソースとサービスの間には、ルーターやロード バランサーなどのネットワーク インフラストラクチャを含む、より多くのハードウェア コンポーネントがあります。 この追加のインフラストラクチャでは、追加の接続待ち時間や一時的な接続エラーが発生することがあります。
クライアントとサーバーの間のネットワーク条件は、特に通信がインターネットを通過する場合に変動する可能性があります。 オンプレミスの場所でも、トラフィックの負荷が大きいと通信が遅くなり、断続的な接続エラーが発生する可能性があります。
課題
一時的な障害は、すべての予測可能な状況で徹底的にテストされている場合でも、アプリケーションの可用性に大きな影響を与える可能性があります。 クラウドでホストされるアプリケーションが確実に動作するようにするには、次の課題に確実に対応できる必要があります。
アプリケーションは、障害が発生したときに障害を検出し、障害が一時的である可能性が高いか、長期間持続するか、または端末障害であるかどうかを判断できる必要があります。 障害が発生したときに異なるリソースから異なる応答が返される可能性があり、これらの応答は操作のコンテキストによっても異なる場合があります。 たとえば、アプリケーションがストレージから読み取っている場合のエラーの応答は、ストレージへの書き込み時のエラーの応答と異なる場合があります。 多くのリソースとサービスには、一時的な障害コントラクトが十分に文書化されています。 ただし、このような情報が利用できない場合は、障害の性質と、それが一時的である可能性があるかどうかを検出することが困難になる可能性があります。
障害が一時的である可能性が高いと判断した場合、アプリケーションは操作を再試行できる必要があります。 また、操作が再試行された回数を追跡する必要もあります。
アプリケーションでは、再試行に適切な戦略を使用する必要があります。 この戦略では、アプリケーションが再試行する回数、各試行間の遅延、および試行の失敗後に実行するアクションを指定します。 適切な試行回数と各試行の間の遅延は、多くの場合、判断が困難です。 この戦略は、リソースの種類と、リソースとアプリケーションの現在の動作条件によって異なります。
一般的なガイドライン
次のガイドラインは、アプリケーションに適した一時的な障害処理メカニズムを設計するのに役立ちます。
組み込みの再試行メカニズムがあるかどうかを判断する
多くのサービスでは、一時的な障害処理メカニズムを含む SDK またはクライアント ライブラリが提供されています。 通常、使用する再試行ポリシーは、ターゲット サービスの性質と要件に合わせて調整されます。 また、サービスの REST インターフェイスは、再試行が適切かどうか、および次回の再試行までの待機時間を判断するのに役立つ情報を返す場合があります。
別の再試行動作をより適切にする特定の十分に理解された要件がない限り、組み込みの再試行メカニズムが使用可能な場合は使用する必要があります。
操作が再試行に適しているかどうかを判断する
再試行操作は、障害が一時的な場合 (通常はエラーの性質で示されます)、再試行時に操作が成功する可能性が少なくともある場合にのみ実行します。 存在しない項目へのデータベースの更新や、致命的なエラーが発生したサービスまたはリソースへの要求など、無効な操作を試みる操作を再試行しても意味がありません。
一般に、再試行を実装するのは、再試行の効果を完全に判断できる場合と、条件が十分に理解され、検証できる場合のみです。 それ以外の場合は、呼び出し元のコードに再試行を実装します。 制御外のリソースやサービスから返されるエラーは時間の経過と同時に変化する可能性があり、一時的な障害検出ロジックを見直す必要がある場合があることに注意してください。
サービスまたはコンポーネントを作成するときは、クライアントが失敗した操作を再試行する必要があるかどうかを判断するのに役立つエラー コードとメッセージを実装することを検討してください。 特に、クライアントが (isTransient 値を返すことによって) 操作を再試行する必要があるかどうかを示し、次の再試行の前に適切な遅延を提案します。 Web サービスを構築する場合は、サービス コントラクト内で定義されているカスタム エラーを返す方法を検討してください。 汎用クライアントは、これらのエラーを読み取ることができない可能性がありますが、カスタム クライアントの作成に役立ちます。
適切な再試行回数と間隔を決定する
再試行回数と間隔をユース ケースの種類に合わせて最適化します。 十分な回数再試行しないと、アプリケーションは操作を完了できず、おそらく失敗します。 再試行回数が多すぎる場合、または試行間隔が短すぎる場合、アプリケーションはスレッド、接続、メモリなどのリソースを長期間保持する可能性があり、アプリケーションの正常性に悪影響を及ぼします。
時間間隔と再試行回数の値を操作の種類に合わせて調整します。 たとえば、操作がユーザー操作の一部である場合は、間隔を短くし、再試行を数回試みる必要があります。 この方法を使用すると、開いている接続を保持し、他のユーザーの可用性を低下させる応答をユーザーに待たないようにすることができます。 操作が実行時間の長いワークフローまたは重要なワークフローの一部であり、プロセスの取り消しと再起動にコストがかかる場合や時間がかかる場合は、試行の間に長く待機し、再試行回数を増やすのが適切です。
再試行間の適切な間隔を決定することは、成功した戦略を設計する上で最も困難な部分であることに注意してください。 一般的な方法では、次の種類の再試行間隔が使用されます。
指数バックオフ。 アプリケーションは、最初の再試行の前に少し待ってから、後続の再試行の間隔を指数関数的に増やします。 たとえば、3 秒後、12 秒後、30 秒後などに操作を再試行できます。
段階的間隔。 アプリケーションは、最初の再試行の前に少し待ってから、後続の再試行の間隔を段階的に増やします。 たとえば、3 秒後、7 秒後、13 秒後などに操作を再試行できます。
一定間隔。 アプリケーションは、各試行の間に同じ期間待機します。 たとえば、3 秒ごとに操作を再試行できます。
即時再試行。 ネットワーク パケットの競合やハードウェア コンポーネントのスパイクなどのイベントが原因で、一時的な障害が短時間発生することがあります。 この場合、アプリケーションがアセンブルして次の要求を送信する時間に障害が解消された場合に成功する可能性があるため、操作を直ちに再試行するのが適切です。 ただし、複数回の即時再試行は実行しないでください。 即時再試行が失敗した場合は、指数バックオフやフォールバック アクションなどの代替戦略に切り替える必要があります。
無作為化。 前述の再試行戦略には、クライアントの複数のインスタンスが後続の再試行を同時に送信しないようにランダム化を含めることができます。 たとえば、1 つのインスタンスが 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 で使用できる組み込みまたは既定の再試行戦略を利用しますが、シナリオに適している場合にのみ使用できます。 通常、これらの戦略は一般的です。 シナリオによっては、必要なものがすべて揃っている場合もありますが、他のシナリオでは、特定の要件に合わせてさまざまなオプションが提供されない場合があります。 最も適切な値を決定するには、設定がアプリケーションに与える影響を理解するためにテストを実行する必要があります。
一時的な障害と非一時的な障害をログに記録して追跡する
再試行戦略の一環として、例外処理と、再試行をログに記録するその他のインストルメンテーションを含めます。 一時的な障害と再試行が予期され、問題は示されません。 ただし、再試行回数が定期的に増加することは、多くの場合、障害を引き起こす可能性がある問題や、アプリケーションのパフォーマンスと可用性を低下させる問題を示す指標です。
一時的な障害をエラー エントリとしてではなく警告エントリとしてログに記録し、監視システムが誤ったアラートをトリガーする可能性のあるアプリケーション エラーとして検出しないようにします。
再試行がサービスの調整によって引き起こされたのか、接続エラーなどの他の種類の障害によって引き起こされたのかを示す値をログ エントリに格納して、データの分析中に区別することを検討してください。 調整エラーの数の増加は、多くの場合、アプリケーションの設計上の欠陥や、専用ハードウェアを提供するプレミアム サービスに切り替える必要性を示しています。
再試行メカニズムを含む操作の全体的な経過時間を測定してログに記録することを検討してください。 このメトリックは、ユーザーの応答時間、プロセスの待機時間、アプリケーションのユース ケースの効率に対する一時的な障害の全体的な影響を示す適切な指標です。 また、応答時間に影響する要因を理解できるように、発生した再試行の数もログに記録します。
操作が成功するまでの失敗の数と割合、平均再試行回数、または全体的な時間が経過したときにアラートを生成できるテレメトリと監視システムを実装することを検討してください。
継続的に失敗する操作を管理する
試行のたびに失敗し続ける操作を処理する方法を検討してください。 このような状況は避けられません。
再試行戦略では、操作を再試行する最大回数を定義しますが、同じ回数の再試行でアプリケーションが操作を繰り返すのを防ぐことはありません。 たとえば、注文処理サービスが致命的なエラーで失敗し、アクションが完全に停止した場合、再試行戦略は接続タイムアウトを検出し、一時的なエラーと見なす可能性があります。 コードは、指定された回数だけ操作を再試行し、その後、終了します。 ただし、別の顧客が注文を行うと、操作は毎回失敗しますが、もう一度試行されます。
継続的に失敗する操作の継続的な再試行を防ぐには、サーキット ブレーカー パターンの実装を検討する必要があります。 このパターンを使用する場合、指定した時間枠内のエラーの数がしきい値を超えた場合、要求はエラーとしてすぐに呼び出し元に戻り、失敗したリソースまたはサービスにアクセスしようとしません。
アプリケーションは、サービスがいつ使用可能になるかを検出するために、断続的かつ長い間隔で定期的にサービスをテストできます。 適切な間隔は、操作の重要度やサービスの性質などの要因によって異なります。 それは数分から数時間の間に何かかもしれません。 テストが成功すると、アプリケーションは通常の操作を再開し、新しく復旧されたサービスに要求を渡すことができます。
それまでは、サービスの別のインスタンス (異なるデータセンターまたはアプリケーション内にある場合) にフォールバックしたり、互換性のある (より単純な) 機能を提供する同様のサービスを使用したり、サービスが間もなく利用可能になることを期待して代替操作を実行したりできます。 たとえば、サービスの要求をキューまたはデータ ストアに格納し、後で再試行することが適切な場合があります。 または、アプリケーションの代替インスタンスにユーザーをリダイレクトしたり、アプリケーションのパフォーマンスを低下させたり、引き続き許容できる機能を提供したり、アプリケーションが現在使用できないことを示すメッセージをユーザーに返したりすることもできます。
その他の考慮事項
再試行回数の値とポリシーの再試行間隔を決定する場合は、サービスまたはリソースに対する操作が実行時間の長い操作とマルチステップ操作のどちらに含まれているかを検討します。 失敗した場合に既に成功した他のすべての操作手順を補正するのは困難または高価な場合があります。 この場合、非常に長い間隔と多数の再試行が許容される可能性があります。その戦略で、不足しているリソースを保持またはロックすることによって他の操作がブロックされない限りです。
同じ操作を再試行すると、データに不整合が発生する可能性があるかどうかを検討してください。 マルチステッププロセスの一部が繰り返され、操作が冪等でない場合、不整合が生じる可能性があります。 たとえば、値をインクリメントする操作が繰り返されると、無効な結果が生成されます。 メッセージをキューに送信する操作を繰り返すと、コンシューマーが重複するメッセージを検出できない場合、メッセージ コンシューマーに不整合が発生する可能性があります。 こうしたシナリオを防ぐために、それぞれのステップをべき等操作として設計します。 詳細については、「べき等性パターン」を参照してください。
再試行される操作のスコープを検討してください。 たとえば、複数の操作を含むレベルで再試行コードを実装し、失敗した場合はそれらすべてを再試行する方が簡単な場合があります。 ただし、これを行うと、冪等性の問題や不要なロールバック操作が発生する可能性があります。
複数の操作を含む再試行スコープを選択する場合は、再試行間隔を決定するとき、操作の経過時間を監視するとき、およびエラーのアラートを発生させる前に、それらのすべての合計待機時間を考慮に入れておきます。
共有アプリケーション内の近隣テナントや他のテナント、および共有リソースとサービスを使用する場合に、再試行戦略がどのように影響するかを検討します。 積極的な再試行ポリシーにより、これらの他のユーザーやリソースとサービスを共有するアプリケーションに対して、一時的な障害が増える可能性があります。 同様に、アプリケーションは、リソースとサービスの他のユーザーによって実装された再試行ポリシーの影響を受ける可能性があります。 ビジネスクリティカルなアプリケーションの場合は、共有されていない Premium サービスを使用できます。 これにより、これらのリソースとサービスの負荷と結果的な調整をより詳細に制御できるため、追加コストを正当化するのに役立ちます。