- Authors

- Name
- Youngju Kim
- @fjvbn20031
- はじめに
- 1. エラーを分類する二つの軸
- 2. 例外と戻り値 — 勝者のいない論争
- 3. 境界でエラーを翻訳する
- 4. HTTP エラー契約: ステータスコードと problem details
- 5. 再試行 — 冪等性なしに再試行なし
- 6. 部分的な失敗とタイムアウト予算
- 7. 観測 — ログ・メトリクス・トレースのエラー
- 8. 利用者に何を伝えるか
- 9. アンチパターン一覧
- クイズ: 理解度を確認しましょう
- おわりに
- 参考資料
- 関連記事
はじめに
ほとんどのコードベースで、成功経路は設計され、失敗経路はただ発生します。成功応答のフィールド名はレビューで論点になるのに、エラー応答は「とりあえず 500 で投げて後で整理しよう」で通ります。その「後で」はたいてい障害の振り返りの席です。
このブログには CORS エラー、サーバを直すべき理由 のように特定のエラーを解剖した記事や、SLI/SLO/エラーバジェットによる信頼性エンジニアリング のようにエラーを予算として扱う記事があります。しかしアプリケーションのエラー設計そのものを扱った記事はありません。本記事がその席を埋めます。失敗を例外の文法の問題ではなくインターフェース契約の一部として置き、エラーが層の境界を越えるたびに何を翻訳し何を捨てるのかを追います。
根拠は RFC 9110、RFC 6585、RFC 9457、グーグル SRE 本のカスケード障害の章、そして OWASP 認証チートシートです。
1. エラーを分類する二つの軸
エラー処理の設計は分類から始まります。分類がなければすべてのエラーが一つの catch (Exception e) に集まり、その地点ではどんな判断も下せません。
1-1. 軸 1 — 想定された失敗かバグか
- 想定された失敗: ドメイン規則上ふつうに起こりうる結果です。残高不足、予約の重複、期限切れのクーポン。これは値であり、関数のシグネチャに現れるべきです。
- バグ: コードの不変条件が壊れた状態です。null 参照、配列の範囲外、到達不能な分岐。これは値ではなく欠陥であり、飲み込んではいけません。
この区別をぼかすと事故が二つ同時に起きます。バグをドメインの結果のように利用者に見せてしまい、ドメインの結果を 500 として報告してアラート疲れを作ります。
1-2. 軸 2 — 再試行できるか
- 再試行できる: 同じリクエストを少し後で送れば成功しうるもの。一時的なネットワークエラー、タイムアウト、429、503。
- 再試行できない: 何度送っても結果が同じもの。400、401、403、404、422、ほとんどの 409。
1-3. 四つの象限と処理方針
| 再試行できる | 再試行できない | |
|---|---|---|
| 想定された失敗 | バックオフして再試行、なお失敗なら利用者に案内 | 直し方を伝えて終了 |
| バグ | 存在しない(あるなら分類が誤り) | ログとアラート、利用者には一般的な文言 |
「想定された失敗でありながら再試行できる」が、最も多くのコードを必要とする象限です。ここが 5 節と 6 節の主題です。
1-4. 三つ目の軸 — 誰の落ち度か
クライアント、こちらのサーバ、下流の依存先。この軸は 4 節の HTTP ステータスコードの選択にそのままつながります。依存先の失敗を 4xx で報告すると、クライアントに直せないものを直せと要求することになります。
2. 例外と戻り値 — 勝者のいない論争
言語コミュニティごとに答えが違い、どちらも相手を説得できていない場所です。勝者を決める代わりに軸を見ます。
例外方式 戻り値方式
呼び出し側が忘れてもコンパイル 呼び出し側が処理を忘れにくい
される
シグネチャに現れないことがある 失敗がシグネチャに現れる
中間層はコードを書かない 層ごとに伝播のコードが要る
遠い場所でまとめて処理する 呼び出し地点で局所的に処理する
2-1. 五つの軸
- 強制力: 呼び出し側が失敗を無視できるか。戻り値方式は無視しにくく、検査なしの例外は簡単に忘れられます。
- シグネチャの可視性: 関数を見るだけでどんな失敗がありうるか分かるか。
- 伝播コスト: エラーを上へ運ぶのに必要な定型コードの量。例外はここで圧倒的に短くなります。
- 処理する場所: 失敗を発生地点の近くで扱いたいか、ずっと上でまとめて扱いたいか。
- 情報の保存: 伝播の過程で原因の連鎖と文脈がどれだけ残るか。
2-2. それぞれが払う代償
例外中心の言語は伝播コストがほぼゼロである代わりに、どんな失敗がどこから出るのかシグネチャだけでは分からないという代償を払います。検査例外はそれをシグネチャへ引き上げる代わりに、インターフェースが実装の詳細に汚染され、開発者が空の catch で迂回する傾向を生みます。戻り値中心の言語は失敗を型で示す代わりに、層ごとに伝播コードを書くことになり、その過程で原因の文脈を付け足す規律が必要になります。
実務的な結論は一つだけです。チームが使う言語の慣用に従うのが最も安いということです。慣用に逆らう選択は、ライブラリの生態系、静的解析ツール、新しく入る人の期待のすべてと同時に戦うことになります。
2-3. 方式に関係なく共通の規則
- 失敗はシグネチャかドキュメントのどちらかに必ず現れなければなりません。
- エラーを飲み込みません。空の catch は情報を消すコードです。
- エラーを文字列メッセージだけで表しません。分岐できる型かコードが要ります。
- 制御フローに例外を使いません。ループの終了を例外で行うコードは、読む人とプロファイラを同時に欺きます。
3. 境界でエラーを翻訳する
エラー設計の大部分は、境界で何を翻訳し何を捨てるかを決める作業です。
外部 API クライアント ─┐
├─▶ ドメインエラー ─▶ HTTP エラー契約 ─▶ 利用者向け文言
データストア ──────────┘ │
└─▶ キューの再処理ポリシー
3-1. 境界ごとの翻訳規則
| 境界 | 入ってくるもの | 出ていくもの | 保存するもの |
|---|---|---|---|
| ストア → ドメイン | 制約違反、接続エラー | ドメインエラー、インフラエラー | 原因の連鎖、再試行可否 |
| 外部 API → ドメイン | ステータスコード、タイムアウト | ドメインエラー | 相手サービス名、相関 ID |
| ドメイン → HTTP | ドメインエラー | ステータスコード + problem details | エラー種別の識別子 |
| ドメイン → キュー | ドメインエラー | 再試行、遅延再試行、デッドレター | 試行回数、最後の原因 |
3-2. 二つのアンチパターン
層の漏れが一つ目です。ストアの制約違反の例外がそのままコントローラまで上がってくると、コントローラがストレージ技術を知る必要が生まれ、ストアを替えた瞬間にコントローラが壊れます。
過剰なラップが二つ目です。層ごとに新しい例外で包みながら原因を捨てると、ログには「処理中にエラーが発生しました」が五重に積まれるだけで、本当の原因は消えます。規則は単純です。包むときは必ず原因を添えて包みます。
3-3. 再試行可否フラグを境界の向こうへ運ぶ
下位の層だけが知っている情報が一つあります。その失敗が一時的かどうかです。この情報をドメインエラーに載せて上げないと、上位の層はメッセージ文字列を見て推測することになります。ドメインエラーの型に再試行可否を明示的な属性として入れることが、本記事が勧める最も安い改善の一つです。
4. HTTP エラー契約: ステータスコードと problem details
4-1. ステータスコードは経路全体が読む値
RFC 9110 の定義でよく取り違えられるものを整理します。400 はクライアント側の誤りにより処理できない場合、401 は「対象リソースに対する有効な認証資格情報がリクエストにない」、403 は「サーバはリクエストを理解したが、履行を拒否する」、409 は「リクエストが対象リソースの現在の状態と競合する」、422 はコンテンツタイプと構文は理解したが指示を処理できない場合です。RFC 9110 は §15.5.2 で 401 を、§15.5.4 で 403 を定義します。401 は認証、403 は認可です。
429 Too Many Requests は RFC 9110 ではなく RFC 6585 にあります。原文は「利用者が与えられた時間内に多すぎるリクエストを送った(レートリミット)」という意味だとし、応答に Retry-After ヘッダを「含めてもよい(MAY)」と書きます。
4-2. 本文は RFC 9457 で
RFC 9457 は RFC 7807 を置き換え、application/problem+json メディアタイプを定義します。§3.1 のメンバーは type(URI 参照、問題タイプの主識別子、ない場合は about:blank)、status(助言的、実際のステータスコードと一致)、title(ローカライズを除けば発生ごとに変わってはならない)、detail(今回の発生の説明)、instance(今回の発生を識別する URI)です。
detail について仕様は「デバッグ情報を与えるよりも、クライアントが問題を正すのを助けることに焦点を当てるべき」と書きます。この一文がエラーメッセージ執筆の基準です。
例 — 再試行可否まで載せたエラー応答。
HTTP/1.1 503 Service Unavailable
Content-Type: application/problem+json
Retry-After: 30
{
"type": "https://api.example.com/problems/upstream-unavailable",
"title": "Upstream service unavailable",
"status": 503,
"detail": "決済承認サービスに一時的に接続できません。30 秒後に同じリクエストを再送してください。",
"instance": "/v1/payments/req_01J9XQ",
"retryable": true,
"traceId": "4bf92f3577b34da6a3ce929d0e0e4736"
}
§3.2 は拡張メンバーを認め、クライアントは「認識できない拡張を必ず無視しなければならない(MUST)」と規定するので、retryable や traceId のようなフィールドを後から足しても破壊的変更にはなりません。
4-3. エラー種別の識別子をクライアント分岐の唯一の根拠に
クライアントが分岐すべきなのは type の値であって detail の文字列ではありません。この規則をドキュメントに明示しないとクライアントはメッセージを正規表現で照合し始め、その瞬間に文言の修正がデプロイを要求するようになります。
5. 再試行 — 冪等性なしに再試行なし
5-1. まず冪等性
RFC 9110 §9.2.2 は idempotent を「複数回の同一リクエストがサーバに意図する効果が、一回のリクエストと同じである」と定義し、GET、HEAD、PUT、DELETE、OPTIONS、TRACE を含めます。POST は冪等ではありません。 したがって POST を再試行するには冪等性キーが必要で、キーがなければそのリクエストは再試行してはいけないリクエストです。この判断がコードに明示的に存在しないシステムでは、再試行が静かに二重決済を作ります。
5-2. バックオフは必ず無作為化する
グーグル SRE 本は再試行をスケジュールするとき「常に無作為化された指数バックオフを使え」と明記します。理由は明快です。固定間隔や純粋な指数バックオフは、同じ時刻に失敗したクライアントを同じ時刻に再び集中させ、それが回復中のサーバをもう一度倒します。
試行 1 失敗 → 待ち = random(0, 1 秒)
試行 2 失敗 → 待ち = random(0, 2 秒)
試行 3 失敗 → 待ち = random(0, 4 秒)
上限に到達 → 諦めてエラーを上へ伝える
5-3. 再試行の予算と上限
同じ文書はプロセス単位の再試行予算を勧めます。例として挙げられている値は「プロセスで毎分 60 回の再試行だけを許す」です。そして「特定のリクエストを無限に再試行するな」と明記します。予算を使い切ったら再試行をやめ、エラーをそのまま上へ上げます。予算のない再試行は、負荷がかかった瞬間に負荷を倍にする装置です。
5-4. 多層の再試行は掛け算になる
最も見落とされる項目です。グーグル SRE 本は、三つの層がそれぞれ 4 回ずつ再試行すると、一度の利用者の操作が 4 × 4 × 4 = 64 回の試行になると指摘します。クライアント SDK、API ゲートウェイ、サービス間クライアントがそれぞれ「妥当な」再試行を有効にしていると、この状況が生まれます。
対処は再試行する層を一つに決めることです。たいていは利用者に最も近い一つの層だけが再試行し、残りの層は失敗をそのまま伝えます。そして再試行の有無をログとトレースに出して、実際の試行回数を観測できるようにします。再試行回数と成功確率の関係は 再試行・累積確率計算機 で感覚をつかめます。
5-5. 何を再試行し、何を再試行しないか
- 再試行する: 接続失敗、タイムアウト(冪等なとき)、429(
Retry-Afterを尊重)、503、一部の 500 - 再試行しない: 400、401、403、404、422、ほとんどの 409
グーグル SRE 本は再試行できるエラーとできないエラーを明確なコードで区別し、恒久的なエラーは絶対に再試行するなと言います。400 を再試行するクライアントは、自分のバグをサーバの負荷に変えます。
5-6. 拒否も戦略である
同じ章は負荷遮断を扱います。無制限にキューイングする代わりに早く拒否せよ、というものです。503 で素早く拒否すればクライアントはバックオフし、サーバは生きているリクエストを処理できます。キューに積むと待ち時間がクライアントのタイムアウトを超え、誰も受け取らない応答を作るために資源を燃やすことになります。回路を開く判断は サーキットブレーカパターン完全ガイド で扱います。
6. 部分的な失敗とタイムアウト予算
6-1. タイムアウトは予算である
利用者向けリクエストに 3 秒の締め切りがあるなら、その 3 秒は下位の呼び出しが分け合う予算です。呼び出しごとに個別のタイムアウトを決めると合計が締め切りを超え、利用者はもう去っているのにサーバは働き続けます。
利用者の締め切り 3000ms
├─ 認証確認 150ms
├─ 注文の取得 400ms
├─ 決済の承認 1500ms (再試行 1 回を含む → 実際の上限は 2 × 700ms)
└─ 余裕 950ms (シリアライズ、GC、ネットワークの揺らぎ)
残り時間を下位の呼び出しへ伝えると、この予算が実際に守られます。上位ですでに 2 秒を使ったあとに呼ばれる下位サービスは、1 秒の締め切りを受け取るべきです。この伝播がなければ、各サービスは自分の基準で最大値まで待ちます。
6-2. タイムアウトと再試行も掛け算になる
タイムアウト 1 秒に再試行 3 回なら最悪 3 秒です。ここに上位の層がさらに再試行すると 5-4 節の掛け算が起きます。タイムアウト予算は再試行を含めて計算する必要があります。
6-3. 部分的な失敗の応答設計
複数件のリクエストで一部だけ失敗したときの契約を先に決めます。選択肢は三つです。
- 全体を失敗として扱う: 単純ですが、成功した作業を戻せる必要があります。
- 項目ごとの状態配列を返す: 各項目に成否とエラー種別を載せます。クライアントは失敗したものだけ再試行できます。
- ジョブリソースとして非同期化する: リクエストを受け付け、進行状態を照会させます。
二つ目が最も広く使われますが、リクエスト全体のステータスコードをどうするかを必ず文書化する必要があります。決めないとクライアントごとに解釈が分かれます。
6-4. カスケード障害
グーグル SRE 本はカスケード障害を「正のフィードバックの結果として時間とともに大きくなる障害」と定義します。あるクラスタが落ちてトラフィックが別のクラスタへ集まるサーバ過負荷、そして CPU・メモリ・スレッド・ファイルディスクリプタの資源枯渇が代表的な原因で、資源枯渇は互いを悪化させます。スレッドが足りなければ遅延が増え、遅延が増えれば待機中のリクエストがより多くのメモリを掴みます。
エラー処理の観点での教訓は一つです。失敗したリクエストに資源を長く掴ませないことです。タイムアウトのない呼び出し、無制限のキュー、無限の再試行は、すべて同じ障害を作ります。
7. 観測 — ログ・メトリクス・トレースのエラー
7-1. エラー一つにログ一つ
最もよくある浪費は層ごとにログを出すことです。五つの層がそれぞれ出せばエラー一つがログ五行になり、エラー率の計算もアラートも一緒に膨らみます。規則は処理する場所で一度だけログを出すことです。中間の層はログの代わりに文脈を付けて伝播します。
例 — エラーログに必ず入れるフィールド。
{
"level": "error",
"traceId": "4bf92f3577b34da6a3ce929d0e0e4736",
"errorType": "upstream_unavailable",
"retryable": true,
"attempt": 2,
"upstream": "payment-gateway",
"durationMs": 1043,
"message": "payment authorization failed after retry"
}
7-2. メトリクスは分母が肝心
エラー数だけ数えると、トラフィックが増えれば自動的に増えます。必要なのはエラー率で、分母が何かがそのまま契約になります。再試行の末に成功したリクエストを成功と数えるか失敗と数えるかで数字が大きく変わるので、利用者視点の成功率と試行単位の成功率を両方計測するほうが安全です。エラー率を SLO に結び付ける方法は SLI/SLO/エラーバジェットによる信頼性エンジニアリング で扱います。
7-3. カーディナリティ爆発を避ける
エラーメッセージの文字列をメトリクスのラベルに使うと時系列が爆発します。ラベルには 1 節の分類から出た有限の集合だけを入れます。エラー種別、再試行可否、相手サービス名くらいです。自由な文字列はログに残し、メトリクスには入れません。
7-4. アラートはエラーではなく消費率に
エラー一つごとにアラートを出すシステムはすぐに無視されます。アラートはエラーそのものではなくエラーバジェットの消費速度に掛けます。そしてアラートの本文に相関 ID とエラー種別を入れて、アラートからトレースへ一度で移動できるようにします。
8. 利用者に何を伝えるか
8-1. セキュリティの境界
RFC 9457 §5 は、問題詳細に含める情報が「慎重に精査されなければならず」、「スタックダンプのような実装の詳細」を露出しないよう明示します。スタックトレース、内部ホスト名、SQL 文、ライブラリのバージョンは、すべて攻撃者にとって有用な情報です。
認証の失敗は特に注意すべき場所です。OWASP 認証チートシートは、アプリケーションが「HTTP と HTML の両方で一般的な(generic)方法で応答」すべきだと述べ、「ログイン失敗: ユーザー ID またはパスワードが正しくありません」のような文言を例に挙げます。ユーザー名の列挙を防ぐためです。同じ文書は「HTTP 応答コードが異なるだけでも、アカウントが有効かどうかが漏れうる」と付け加えます。文言だけ揃えてステータスコードや応答時間が分かれていては防御になりません。
8-2. 利用者向けメッセージの三要素
- 何が起きたか: 専門用語なしで一文
- 何ができるか: 再試行、値の修正、待機、問い合わせのいずれか
- 問い合わせ用の識別子: 相関 ID。これがないとサポートがログから事象を見つけられません
8-3. 待つ時間を伝える
再試行できるエラーなら、どれだけ待てばよいかを伝えます。RFC 6585 は 429 の応答が Retry-After ヘッダを含めてもよい(MAY)としており、同じヘッダは 503 にも使えます。UI はこの値を読んで「30 秒後に自動で再試行します」と見せればよいのです。これ一つで利用者の手動リロードが大きく減り、その分サーバの負荷も減ります。
8-4. 機械向けの値と人向けの文を分ける
type は機械向けで、title と detail は人向けです。仕様が title を発生ごとに変えるなと言う理由もここにあります。人向けの文はローカライズや文言改善で頻繁に変わるべきで、機械向けの値は決して変わってはいけません。
9. アンチパターン一覧
- 空の catch: エラーを消すコードです。最低でもログを出し、意図的に無視するなら理由をコメントではなくコードで表します。
- すべてのエラーを 500 に: クライアントが直せるエラーと直せないエラーが混ざり、再試行ロジックとアラートが同時に壊れます。
- 200 にエラーを載せる: プロキシ・監視・再試行ミドルウェアのすべてが成功と読みます。
- エラーを null で返す: 呼び出し側は「値がない」と「失敗した」を区別できず、null チェックと例外処理が混ざります。
- 文字列照合で分岐: エラーメッセージが契約になり、文言の修正が障害になります。
- タイムアウトのない呼び出し: 一つの遅い依存先がスレッドプール全体を塞ぎます。
- 多層の再試行: 4 × 4 × 4 = 64 回の問題。再試行する層を一つに決めます。
- 層ごとのログ: エラー率が膨らみ、アラートが信用を失います。
- 原因を捨てるラップ: 包むときは必ず原因の連鎖を添えます。
- 制御フローに例外: 性能と可読性を同時に失います。
- 利用者にスタックトレースを露出: RFC 9457 §5 が明示的に禁じるパターンです。
- 再試行予算がない: 負荷がかかった瞬間に負荷を倍にします。
クイズ: 理解度を確認しましょう
クイズ 1: 決済リクエストがタイムアウトしました。クライアントは自動で再試行してよいでしょうか?
答え: リクエストが冪等性キーを含み、サーバがそのキーに対応しているときだけ安全です。そうでなければ再試行してはいけません。
解説: タイムアウトはリクエストがサーバに届いたかどうかすら教えてくれません。RFC 9110 §9.2.2 が定義する冪等性は GET、HEAD、PUT、DELETE、OPTIONS、TRACE に適用され POST には適用されないので、POST の決済リクエストを再試行すると二重承認を作りうります。冪等性キーがあればサーバが同じキーの二回目を一回目の結果で応答するので安全になります。再試行可否をエラー応答に明示的に載せれば、クライアントは推測しなくて済みます。
クイズ 2: 障害中にエラー率のダッシュボードが平常の五倍に跳ねましたが、実際の失敗リクエスト数はそこまでではありませんでした。まず何を疑いますか?
答え: 層ごとにログを出しているか、再試行の試行がそれぞれエラーとして集計されている可能性です。
解説: エラー一つが五つの層でそれぞれログに出れば指標は五倍になります。再試行も同じで、試行単位で数えると 3 回の再試行が 3 件のエラーになりますが、利用者視点では失敗 1 件です。対処は処理する場所で一度だけログを出すこと、そして利用者視点の成功率と試行単位の成功率を分けて計測することです。二つの数字が両方必要で、片方だけ見るとそれぞれ別の方向に誤ります。
クイズ 3: クライアント SDK、ゲートウェイ、サービス間クライアントがそれぞれ 4 回の再試行に設定されています。何が問題でしょうか?
答え: 再試行が掛け算になり、利用者の操作一度が最大 4 × 4 × 4 = 64 回のリクエストになります。
解説: グーグル SRE 本がカスケード障害を扱いながら挙げる代表的な増幅経路です。下位サービスがすでに負荷で遅くなっている状態でこの増幅がかかると、回復中のサービスをもう一度倒します。対処は再試行する層を一つに決めて残りは失敗をそのまま伝えること、そしてプロセス単位の再試行予算を置くことです。同じ文書は例としてプロセスで毎分 60 回の再試行制限を挙げ、特定のリクエストを無限に再試行するなと明記します。
クイズ 4: ログイン失敗時に「存在しない ID です」と「パスワードが違います」を分けて見せています。どんなリスクがありますか?
答え: ユーザー名の列挙が可能になります。攻撃者はどのアカウントが実在するか確認できます。
解説: OWASP 認証チートシートは、アプリケーションが HTTP と HTML の両方で一般的な方法で応答すべきだと述べ、「ログイン失敗: ユーザー ID またはパスワードが正しくありません」のような文言を例に挙げます。注意すべきは、文言だけ揃えても足りないことです。同じ文書は HTTP 応答コードが異なるだけでもアカウントの有効性が漏れうると指摘します。応答時間の差も同じ情報を流すので、併せて揃える必要があります。
クイズ 5: 利用者向けリクエストの締め切りが 3 秒なのに、下位の呼び出し四つにそれぞれ 2 秒のタイムアウトが掛かっています。何が誤っていますか?
答え: 個別のタイムアウトの合計が締め切りを大きく超えています。タイムアウトは個別の値ではなく、締め切りから分け合う予算であるべきです。
解説: 最悪 8 秒かかり、その間に利用者はもう去っているのにサーバは資源を掴んで働き続けます。これがカスケード障害の資源枯渇の経路です。対処は締め切りから逆算して各呼び出しの予算を配り、残り時間を下位へ伝播することです。そして再試行があるなら、その掛け算まで予算に含めて計算する必要があります。
クイズ 6: エラー応答の detail の文言を整えたら、モバイルアプリの特定の画面が動かなくなりました。根本原因は何でしょうか?
答え: クライアントが人向けの文を分岐条件に使っていました。機械向けの値と人向けの文が分離されていなかったのです。
解説: RFC 9457 は type を問題タイプの主識別子とし、detail は今回の発生についての説明で「クライアントが問題を正すのを助けることに焦点を当てるべき」と規定します。つまり detail は人が読む文であり、いつでも変わりえます。クライアントが分岐すべき値は type であり、この規則を API ドキュメントに明示する必要があります。明示しなければ、文言の改善もローカライズもすべて破壊的変更になります。
おわりに
エラー処理は文法の問題ではなく契約の問題です。どんな失敗がありうるか、その失敗は再試行できるか、境界を越えるとき何が保存されるか、利用者に何を伝えるか。そのすべてが契約であり、契約だからドキュメントに書かれるべきで、変えれば破壊的変更になります。
最も安い改善を三つ挙げるとこうなります。第一に、ドメインエラーの型に再試行可否を明示的な属性として入れます。上位の層が文字列を見て推測しなくなります。第二に、再試行する層を一つに決め、残りの層は失敗をそのまま伝えます。4 × 4 × 4 の問題が消えます。第三に、エラー応答を RFC 9457 の形式に統一し、クライアントは type だけで分岐するとドキュメントに明記します。文言を直す自由が手に入ります。
三つとも新しいライブラリを必要とせず、三つとも障害一度の費用よりはるかに安く付きます。
参考資料
- RFC 9110 — HTTP Semantics — §9.2.2 の idempotent 定義と対象メソッド、POST が冪等でないこと、400・401・403・409・422・500・503 の定義、§15.5.2 と §15.5.4 による 401(認証)と 403(認可)の区別を引用しました。2026-08-15 確認。
- RFC 6585 — Additional HTTP Status Codes — 429 の定義(「与えられた時間内に多すぎるリクエストを送った」)と
Retry-Afterヘッダが MAY であることを引用しました。2026-08-15 確認。 - RFC 9457 — Problem Details for HTTP APIs — RFC 7807 を置き換えること、
application/problem+jsonメディアタイプ、§3.1 の五つのメンバーとdetailが問題解決を助けることに焦点を当てるべきという規定、§3.2 の拡張メンバーとクライアントの無視義務、§5 のスタックダンプ露出の禁止を引用しました。2026-08-15 確認。 - Addressing Cascading Failures — Google SRE Book — カスケード障害の定義(「正のフィードバックの結果として時間とともに大きくなる障害」)、サーバ過負荷と資源枯渇という原因、「常に無作為化された指数バックオフを使え」、毎分 60 回の再試行予算の例、無限の再試行の禁止、三つの層が 4 回ずつ再試行すると 64 回になるという指摘、再試行できるエラーとできないエラーの区別、早く拒否する負荷遮断を引用しました。2026-08-15 確認。
- Authentication Cheat Sheet — OWASP — 認証失敗時に HTTP と HTML の両方で一般的な方法で応答すべきという勧告と例の文言、HTTP 応答コードの差だけでもアカウントの有効性が漏れうるという指摘を引用しました。2026-08-15 確認。
- エラー分類の二つの軸と四象限、境界ごとの翻訳規則の表、再試行可否フラグをドメインエラーに載せる方法、タイムアウト予算の配分、エラーログの必須フィールド、アンチパターン一覧は、上記資料にそのまま出てくるものではなく、この記事で整理した手順です。
関連記事
- このブログの関連記事: CORS エラー、サーバを直すべき理由
- このブログの関連記事: 冪等性と再試行: 信頼できる API
- このブログの関連記事: サーキットブレーカパターン完全ガイド
- このブログの関連記事: SLI/SLO/エラーバジェットによる信頼性エンジニアリング
- 関連ツール: 再試行・累積確率計算機
- 関連ツール: HTTP Status Codes
完全ガイドシリーズ
- 前の記事: 認証と認可完全ガイド: 仕様の原文で押さえる十の誤解
- 次の記事: 並行性完全ガイド: 共有状態を減らしていく順番