エラーリカバリー:大規模環境におけるグレースフルデグラデーション
SchemaBridge Team · 2026-01-16 · Resilience, Error Handling, Fault Tolerance
大規模環境でのリトライとバックオフの扱い方。マネージドなレジリエンスが「リトライストーム」をどう防ぐか。
重要なポイント 素朴なリトライロジックは障害を増幅させ、叩き続けている当のサービスそのものを落とすリトライストームを引き起こします。レジリエンスは、上限のあるリトライ(一時的な HTTP 障害に対する短い指数バックオフと、バックエンドが過負荷を報告したときの適応的なペーシング)に加え、新しいワークフローバージョン向けの自動ロールバック付き段階的ロールアウト、そして自動化に判断させるべきでないすべてのことに対する人による復旧ツール(Retry、Resolve、Start over)によって実現されます。
「リトライストーム」:素朴なエラーハンドリングが失敗する理由
分散システムの初期には、「エラーハンドリング」といえば、コードの一部を try/catch で囲み、場合によっては 3 回リトライする単純な while ループを追加することを意味していました。障害がまれで局所的な、小規模で隔離された環境ならこれでも機能します。しかし大規模環境、つまり相互に接続されたマイクロサービスのメッシュでは、素朴なリトライはリトライストーム(または「Thundering Herd」)と呼ばれるシステム全体の崩壊を招くレシピです。
ブラックフライデーのセール中、高負荷によってプライマリデータベースがわずかに遅くなったとしましょう。クエリのレイテンシは 10ms から 1100ms に増加します。API には標準的な 1 秒のタイムアウトが設定されています。突然、1,000 の並行ワーカーが一斉にこのタイムアウトに達します。全員がエラーを捕捉し、単純なループのロジックに従って即座にクエリをリトライします。
すると、最初の 1,000 リクエストの処理ですでに苦しんでいたデータベースに、さらに 1,000 リクエストが一度に押し寄せます。負荷は瞬時に倍増します。データベースの CPU は 100% に跳ね上がり、レイテンシは 5 秒に増加します。ワーカーは再び失敗し、再びリトライします。システムは障害の正のフィードバックループに陥ります。事実上、自らのインフラに DDoS 攻撃を仕掛けたことになります。軽微なパフォーマンス低下を、システム全体の停止に変えてしまったのです。
障害を受け入れる:エラーは API の一部である
SchemaBridge では、エラーを「例外的なもの」とする考え方から離れています。イベント駆動の世界では、失敗は成功と同じくらいありふれたものです。ネットワークは分断され、Pod は予測不能な挙動をし、サードパーティ API にはメンテナンス時間があります。私たちはエラーリカバリーをコード内の catch ブロックとしてではなく、プラットフォーム上のマネージドライフサイクルとして扱います。そして、自動でリトライする対象には意図的に厳しい制限を設けています。
SchemaBridge は障害をどう分類するか
効果的に復旧するには、まず何かがなぜ失敗したのかを理解する必要があります。SchemaBridge はこの判断を 2 つのレイヤーで行います。
| レイヤー | 障害 | 自動的に行われること |
| :--- | :--- | :--- |
| HTTP 呼び出し(Gateway) | 5xx、429、または I/O エラー(タイムアウト、接続リセット) | プロセス内でリトライ。指数バックオフで最大 3 回の追加試行 |
| HTTP 呼び出し(Gateway) | その他の 4xx(400、401、403、404…) | リトライしない。間違ったパスワードは 2 回目でも決して成功しない |
| ステップ(vertex) | レート制限のあるバックエンドの一時的な過負荷(external_overload) | vertex ごとの上限まで自動的に再ディスパッチ |
| ステップ(vertex) | それ以外のすべて | 初回の試行で終了扱い。実行は赤で表示され、オペレーターを待つ |
デフォルトは意図的に保守的です。ステップの失敗は一時的であると証明されない限り恒久的とみなされます。繰り返しても安全とは限らない副作用を黙って再実行するくらいなら、失敗を人に見せるほうがよいと考えています。
上限付きリトライの構造:バックオフの数学
HTTP 呼び出しの指数バックオフ
Gateway 呼び出しが一時的なエラーに遭遇しても、プラットフォームは宛先を毎秒叩き続けたりはしません。試行の間隔を指数的に延ばしていきます。
$$Wait = Base \times 2^{Retry - 1}$$
- 初回の試行が失敗
- リトライ 1:1 秒待機
- リトライ 2:2 秒待機
- リトライ 3:4 秒待機
合計 4 回の試行で、約 7 秒にわたります。宛先が Retry-After ヘッダー付きで 429 Too Many Requests を返した場合、プラットフォームはそれに従います(上限 30 秒)。この短く上限のある段階的な待機により、苦しんでいるサービスを犠牲にすることなく、ネットワークの一時的な揺らぎを吸収できます。なお、このバックオフはランダム化されていません(ジッターはありません)。つまり、一時的な揺らぎをならすための設計であり、何千もの独立したクライアントを協調させるためのものではありません。
過負荷のバックエンドに対する適応的な再ディスパッチ
本当に飽和しているバックエンドに対して、短いバックオフは適切な手段ではありません。レート制限のあるステップタイプ(Gateway ステップとエージェント型ステップ)では、過負荷による失敗がプラットフォームの宛先ごとのアドミッション制御にフィードバックされ、その宛先への作業の受け入れレートが引き下げられます(AIMD コントローラーで、TCP が輻輳制御に使うのと同じ考え方です)。その後、失敗したステップは同じアドミッション経路を通じて自動的に再ディスパッチされます。デフォルトでは最大 3 回で、vertex ごとに maxAutoRetries で設定できます(副作用を決して再送してはならないステップでは 0 に設定してください)。
{
"type": "GATEWAY",
"config": {
"url": "https://api.example.com/v1/charges",
"method": "POST",
"maxAutoRetries": 5
}
}
ペーシングが固定タイマーではなくアドミッション制御から来るため、リトライは宛先が必要とするちょうどその分だけ減速します。過負荷が上限を超えて続いた場合、ステップは失敗し、実行はオペレーターを待ちます。
段階的ロールバック:ワークフローリリースの安全性
リトライが無意味な場合もあります。新しいワークフローバージョンがバグを持ち込んだなら、いくらリトライしても直りません。ロールバックが必要です。
SchemaBridge は、ワークフローバージョン向けに自動ロールバック付きの段階的ロールアウトをサポートしており、システムワークフロー(gradual_deploy)として実装されています。
- 段階的な引き上げ:設定可能な割合のトラフィック(デフォルトは 10%、最小 1%)が新しいバージョンにルーティングされ、その割合は一定間隔で段階的に引き上げられます。
- ヘルスチェック:各段階で、監視用の実行が新しいバージョンのエラー数を固定のしきい値と比較します。しきい値を超えると、ロールアウトは新しいバージョンのトラフィックを 0% に戻します。
これにより、不具合のあるワークフローリリースの影響範囲は、チェックが検知した時点までに到達していたトラフィックの割合に限定されます。これはワークフローバージョンに適用されるもので、インフラ構成には適用されません。また、これはセーフティネットであって保証ではありません。しきい値に達する前に新しいバージョンが受け取ったトラフィックは、やはり影響を受けています。
人が介在する復旧:オペレーターのツールキット
ループではなく、頭脳を必要とする障害もあります。銀行の照合で請求額と 0.01 ドル合わないためにワークフローが失敗した場合、どう対処すべきかをコードが決めることはできませんし、決めるべきでもありません。
SchemaBridge では、そのような実行は単に FAILED で終了し、オペレーターが対処するために必要なものがすべて揃っています。
- 可視性:失敗したステップは、実行グラフとモニター上で赤くマークされます。
- フォレンジック:オペレーターはステップの入力、出力、エラーメッセージ(例:「mismatch: 100.00 vs 100.01」)を確認できます。
- 介入:オペレーターは適切な修復方法を選びます。
- Retry は失敗したステップを再実行し、そこからワークフローを続行します。
- Resolve & continue は失敗を受け入れ、そのステップにオペレーターが指定した出力を記録して、ワークフローの残りを進めます。
- Start over はワークフロー全体を新しい実行として再実行します。
- Resume on new version は、失敗した実行を、バグ修正済みのワークフローバージョン上で障害発生地点から再開します。
これにより、「エラー」はパニックやデータベースの手作業による修正の元ではなく、標準的な運用ワークフローになります。
例示シナリオ:決済ゲートウェイの障害
毎晩深夜 0 時に大量の更新をバッチ処理しているサブスクリプション型 SaaS 企業を考えてみましょう。ある夜、主要な決済ゲートウェイがある地域で長時間の障害に見舞われます。
従来のやり方
単純な cron ジョブで構築されたレガシーシステムは、ゲートウェイがダウンすると 3 回リトライし、その後サブスクリプションを「失敗 / 決済拒否」としてマークします。エンジニアが目を覚ます頃には、実際にはゲートウェイが単にダウンしていただけなのに、システムが決済は拒否されたと判断したため、顧客アカウントが無効化されているかもしれません。サポートのキューはあふれ、「アカウント解約」メールの後には解約が続きます。
SchemaBridge のやり方
では、同じ課金エンジンを SchemaBridge 上で動かした場合を、正直に描いてみましょう。
1. 上限付きリトライ:503 が出始めると、各課金呼び出しは短いバックオフでリトライされます。一時的な揺らぎは吸収されますが、数時間続く障害は吸収されません。それらの更新処理の実行は失敗します。
2. 失敗は判断ではない:失敗したステップは「拒否」の経路に流れるのではなく実行を停止させるため、拒否と誤認された障害によって誰かのアカウントが解約されることはありません。失敗した実行はゲートウェイのエラーが添付された状態で、モニター上に赤く表示されたまま残ります。
3. 復旧は一括操作:ゲートウェイが正常に戻ったら、オペレーターが失敗した実行を選択し、一括でリトライします。
期待される結果
障害そのものは起こりますし、朝になれば誰かが対処しなければならないことも変わりません。変わるのは被害の大きさです。誤った解約も、不要なメールもなく、復旧はデータベースのクリーンアップではなく 1 回の一括操作で済みます。これこそがレジリエンスのビジネス価値です。
フォールトトレラント設計のためのエキスパートチェックリスト
レジリエントなシステムを構築するには、次のヒューリスティクスに従ってください。
1. すべての書き込みに取り消し方法を計画する:レコードを作成するなら、失敗時にそれをどう削除またはアーカイブするかを把握しておきましょう。たとえその取り消しが人の手で起動するステップであってもです。対称性は一貫性の鍵です。
2. 自分が管理するクライアントのリトライにはジッターを加える:多数の独立したクライアントが同じスケジュールでリトライすると、ランダム性がなければ同期して群れになってしまいます。ランダム性はそれを防ぎます。
3. 誤った分岐より停止した実行を選ぶ:インフラの障害をビジネス上の結果に見せかけてはいけません。人を待つ失敗した実行のほうが、黙って誤った実行よりも優れています。
4. エラー経路を監査する:私たちは「ハッピーパス」はテストしても、「失敗パス」は見落としがちです。ステージング環境で、503 と 400 を返すエンドポイントにステップを向け、それぞれの場合に実行が期待どおりに振る舞うことを確認してください。
結論:失敗はレジリエンスを高める機会である
エラーリカバリーとは、バグを防ぐことではなく、災害を防ぐことです。自動リトライに上限を設け、過負荷によってシステムが殺到するのではなく減速するようにし、オペレーターに的確な復旧ツールを与えることで、「リトライストーム」を恐れることなく複雑なインテグレーションを構築する自由が得られます。
Building the Bridge シリーズの一部です。あわせて読みたい:ビジュアルロジックでクラウドリソースを管理する方法について解説したワークフローとしてのインフラストラクチャ。