再試行パターン:レジリエントなアプリケーションの構築

  1. コンテキストと問題
  2. ソリューション
  3. 再試行戦略
  4. 実装の考慮事項
  5. テストと検証
  6. このパターンを使用する場合
  7. サーキットブレーカーとの組み合わせ
  8. 関連パターン
  9. 参考文献

アプリケーションがリモートサービス(データベース、API、メッセージキュー)と通信する際、問題が発生する可能性があります。ネットワークの一時的な問題、ビジーなサーバー、または一時的なタイムアウトにより、リクエストが失敗する可能性があります。再試行パターンは、アプリケーションがこれらの一時的な不具合を適切に処理し、潜在的な失敗を成功に変えるのに役立ちます。

コンテキストと問題

分散システムは、定期的に一時的な障害に直面します:

  • ネットワーク接続の喪失:コンポーネント間の短い切断
  • サービスの利用不可:デプロイまたは再起動中の一時的なサービス停止
  • タイムアウト:高負荷下でサービスが応答に時間がかかりすぎる
  • スロットリング:圧倒されたときにサービスがリクエストを拒否する

これらの障害は通常、自己修正されます。一時的に過負荷になっているデータベースは、今は接続を拒否するかもしれませんが、バックログをクリアした後、1秒後に接続を受け入れる可能性があります。再試行メカニズムがないと、アプリケーションはこれらの一時的な問題を永続的な障害として扱い、不必要にユーザーエクスペリエンスを低下させます。

ソリューション

一時的な障害を予期し、透過的に処理するようにアプリケーションを設計します。再試行パターンは、失敗した操作を自動的に再試行するメカニズムを導入し、ビジネス機能への影響を最小限に抑えます。

graph LR A["アプリケーション"] --> B["再試行ロジック"] B --> C["リモートサービス"] C -->|"成功"| D["結果を返す"] C -->|"一時的な障害"| E["待機して再試行"] E --> C C -->|"最大再試行回数超過"| F["例外を処理"] style A fill:#e1f5ff style B fill:#fff4e1 style C fill:#ffe1e1 style D fill:#d3f9d8

💡 組み込みの再試行メカニズム

多くのモダンなクライアントライブラリとフレームワークには、設定可能な再試行ロジックが含まれています。カスタム再試行コードを実装する前に、ライブラリのドキュメントを確認してください。

!!!

再試行戦略

障害のタイプとアプリケーションの要件に基づいて再試行戦略を選択します:

1. キャンセル

使用する場合:障害が永続的な問題を示しているか、再試行しても成功しない操作。

  • 認証の失敗
  • 無効なリクエストパラメータ
  • リソースが見つからないエラー

アクション:操作を直ちにキャンセルし、例外を報告します。

2. 即座に再試行

使用する場合:障害が異常またはまれである場合(破損したネットワークパケットなど)。

  • ランダムなネットワーク伝送エラー
  • 一時的な接続リセット

アクション:遅延なしでリクエストを即座に再試行します。

3. 遅延後に再試行

使用する場合:障害が一般的で、接続またはサービス負荷に関連している場合。

  • 接続タイムアウト
  • サービスビジー応答
  • スロットリングエラー

アクション:再試行する前に待機し、次の遅延戦略のいずれかを使用します:

固定遅延:各再試行の間に同じ時間待機します。

試行1 → 2秒待機 → 試行2 → 2秒待機 → 試行3

増分遅延:待機時間を線形に増やします。

試行1 → 2秒待機 → 試行2 → 4秒待機 → 試行3 → 6秒待機 → 試行4

指数バックオフ:各失敗後に待機時間を2倍にします。

試行1 → 1秒待機 → 試行2 → 2秒待機 → 試行3 → 4秒待機 → 試行4 → 8秒待機 → 試行5

ジッターを伴う指数バックオフ:指数遅延にランダム性を追加して、複数のクライアントが同時に再試行するのを防ぎます(「サンダリングハード」問題)。

実装の考慮事項

ログ戦略

アラート疲労を避けるために、障害を適切にログに記録します:

  • 初期の障害:情報エントリとしてログに記録
  • 成功した再試行:デバッグレベルでログに記録
  • 最終的な障害:すべての再試行が使い果たされた後にのみエラーとしてログに記録

このアプローチにより、オペレーターは自己修正の問題に対するアラートで溢れることなく可視性を得ることができます。

graph TB A["リクエスト失敗"] --> B["ログ: INFO - 試行1失敗"] B --> C["待機して再試行"] C --> D["リクエスト再び失敗"] D --> E["ログ: INFO - 試行2失敗"] E --> F["待機して再試行"] F --> G{"成功?"} G -->|"はい"| H["ログ: DEBUG - 試行3で成功"] G -->|"いいえ (最大再試行回数)"| I["ログ: ERROR - すべての再試行が使い果たされました"] style A fill:#ffe1e1 style H fill:#d3f9d8 style I fill:#ff6b6b

パフォーマンスへの影響

ビジネス要件に合わせて再試行ポリシーを調整します:

インタラクティブアプリケーション(Webアプリ、モバイルアプリ):

  • 再試行回数を少なくして高速に失敗する
  • 試行間の短い遅延を使用する
  • ユーザーフレンドリーなメッセージを表示する(「後でもう一度お試しください」)

バッチアプリケーション(データ処理、ETLジョブ):

  • より多くの再試行回数を使用する
  • より長い遅延で指数バックオフを採用する
  • 速度よりも完了を優先する

⚠️ 積極的な再試行を避ける

積極的な再試行ポリシー(最小限の遅延で多くの再試行)は、次のことによって状況を悪化させる可能性があります:

  • すでに過負荷のサービスをさらに劣化させる
  • アプリケーションの応答性を低下させる
  • システム全体でカスケード障害を作成する

失敗したサービスを圧倒するのを防ぐために、再試行と並行してサーキットブレーカーパターンの実装を検討してください。

!!!

冪等性

再試行を適用する前に、操作が冪等(複数回実行しても安全)であることを確認してください。非冪等操作は、意図しない副作用を引き起こす可能性があります:

問題のシナリオ

  1. サービスがリクエストを受信し、正常に処理します
  2. ネットワークの問題により、サービスが応答を送信できません
  3. クライアントが再試行し、重複処理を引き起こします

ソリューション

  • 操作を自然に冪等に設計する
  • 一意のリクエスト識別子を使用して重複を検出する
  • サーバー側の重複排除ロジックを実装する

例外タイプ

異なる例外には異なる再試行戦略が必要です:

例外タイプ 再試行戦略
一時的なネットワークエラー 遅延して再試行 接続タイムアウト、DNS解決失敗
サービスビジー/スロットリング 指数バックオフで再試行 HTTP 429、HTTP 503
認証の失敗 直ちにキャンセル 無効な資格情報、期限切れのトークン
無効なリクエスト 直ちにキャンセル HTTP 400、不正なデータ
リソースが見つからない 直ちにキャンセル HTTP 404

トランザクションの一貫性

トランザクション内で操作を再試行する場合:

  • 成功確率を最大化するために再試行ポリシーを微調整する
  • トランザクションステップをロールバックする必要性を最小限に抑える
  • 分散シナリオの補償トランザクションを検討する
  • 再試行ロジックがトランザクション分離レベルに違反しないことを確認する

テストと検証

🧪 テストチェックリスト

  • さまざまな障害条件(タイムアウト、接続エラー、サービスの利用不可)に対してテストする
  • 通常および障害シナリオでのパフォーマンスへの影響を確認する
  • ダウンストリームサービスへの過度の負荷がないことを確認する
  • 同時再試行での競合状態を確認する
  • さまざまな障害段階でのログ出力を検証する
  • トランザクションロールバックシナリオをテストする

!!!

ネストされた再試行ポリシー

複数の再試行ポリシーを重ねることを避けます:

問題:タスクA(再試行ポリシーあり)がタスクB(再試行ポリシーもあり)を呼び出します。これにより、指数的な再試行回数と予測不可能な遅延が発生します。

ソリューション:下位レベルのタスクを高速に失敗し、障害を報告するように設定します。上位レベルのタスクが独自のポリシーに基づいて再試行を処理できるようにします。

graph TB A["タスク A
(再試行ポリシー)"] --> B["タスク B
(再試行なし)"] B -->|"高速に失敗"| A A -->|"ポリシーに基づいて再試行"| B style A fill:#e1f5ff style B fill:#fff4e1

このパターンを使用する場合

再試行パターンを使用する場合

  • アプリケーションがリモートサービスまたはリソースと対話する
  • 障害が一時的で短命であることが予想される
  • 失敗したリクエストを繰り返すことで成功する可能性が高い
  • 操作が冪等であるか、冪等にすることができる

再試行パターンを使用しない場合

  • 障害が長期間続く可能性が高い(代わりにサーキットブレーカーを使用)
  • 非一時的な障害を処理する(ビジネスロジックエラー、検証失敗)
  • スケーラビリティの問題に対処する(代わりにサービスをスケールする)
  • 操作に重大な副作用があり、冪等ではない

サーキットブレーカーとの組み合わせ

再試行パターンとサーキットブレーカーパターンは互いに補完します:

  • 再試行:操作を再度試行することで一時的な障害を処理します
  • サーキットブレーカー:サービスがダウンしていることがわかっている場合に再試行を防ぎます
stateDiagram-v2 [*] --> Closed: 通常動作 Closed --> Open: 障害しきい値超過 Open --> HalfOpen: タイムアウト経過 HalfOpen --> Closed: 成功 HalfOpen --> Open: 失敗 note right of Closed リクエストが通過 失敗時に再試行 end note note right of Open リクエストが直ちに失敗 再試行は試みられない end note note right of HalfOpen 限定的なリクエストが許可 サービス回復をテスト end note

これらのパターンを組み合わせることで、包括的な障害処理が提供されます:

  1. 再試行が一時的な不具合を処理します
  2. サーキットブレーカーが失敗したサービスを圧倒するのを防ぎます
  3. 長期間の停止中でもシステムが応答性を維持します

関連パターン

サーキットブレーカー:失敗する可能性が高い操作を繰り返し実行しようとするのを防ぎ、障害が修正されるのを待たずに続行できるようにします。

スロットリング:アプリケーションインスタンス、サービス、またはテナントによるリソースの消費を制御します。

レート制限:サービスに送信されるリクエストのレートを管理して、圧倒するのを避けます。

参考文献

シェア