gRPCの呼び出しが落ちたとき、手元に残るのは数値ひとつと短い文字列だけです。HTTPのつもりで404や500を眺める感覚のまま向き合うと、ログにはUNKNOWNとINTERNALばかりが並ぶ。原因はコードの数が足りないことではなく、16種のエラーコードをどう割り当て、受け取った側がどう分岐するかを決めていない点にあります。扱う範囲は、16種の意味、HTTPとの対応、再試行してよい範囲、詳細の返し方、返すコードの絞り込みまで。プロトコルの基礎はgRPCの仕組みとRESTとの違いに譲ります。
まとめ|先に決める3項目とコードを選ぶときの起点
着手前に決めるのは3つです。サーバーが返してよいコードをどこまで絞るか。再試行してよいコードをどれにするか。コードだけでは足りない理由をどの型で運ぶか。ここを決めずに実装へ入ると、サーバーは例外をそのまま流してUNKNOWNを量産し、クライアントは一律リトライを掛けて障害を長引かせます。
選び分けの起点は「呼び出し側が次に何をすればよいか」に置くと迷いません。入力を直せば通るならINVALID_ARGUMENT、待って再送すれば通るならUNAVAILABLE、何をしても通らないならPERMISSION_DENIED。この基準に統一すると対応表もリトライ設定も一本の筋で決まります。
gRPCのステータスコードとは|grpc-statusで返る17個の終了コード
ステータスコードの内訳|OKを含む17個で呼び出しの終了状態を表す
gRPC公式のStatus codesは、0番のOKから16番のUNAUTHENTICATEDまで連番でコードを定義しています。合計17個のうち成功を表すのはOKひとつで、残る16個がエラーコードです。「16種」と呼ばれるのはこの16個で、OKを数に入れるかどうかの違いに過ぎません。
返却の実体は、応答のトレーラに載るgrpc-statusという数値のメタデータで、人が読む短い文字列をgrpc-messageで添えられます。この2つが標準のエラーモデルにあたります。
HTTPのステータスコードとは層が違う|本体の応答は常に200
混乱の元になりやすいのが、gRPCの通信そのものはHTTP/2の上を走るという事実です。メソッドがエラーで終わってもHTTPのレスポンス自体は200で返り、失敗の事実はgrpc-statusだけが持つ。ロードバランサのログを200で埋めながらアプリケーションは失敗し続ける状態が普通に起こります。
逆にHTTP側が200以外で返るなら、gRPCのメソッドまで到達していない合図です。経路上のプロキシが拒否した、パスが存在しない、といったケースにあたる。HTTP側の番号の意味はHTTPステータスコード一覧と確認方法にまとめてあります。
誰が生成するか|ライブラリが返すコードとアプリケーションが返すコード
16種のうち一部は、アプリケーションのコードを一行も書かずに返ってきます。gRPC公式のstatuscodes.mdは、キャンセルでCANCELLED、メソッド不在でUNIMPLEMENTED、停止中や接続断でUNAVAILABLE、サーバー側の例外送出でUNKNOWN、メッセージサイズ超過でRESOURCE_EXHAUSTEDが生成されると列挙しています。
反対に、ライブラリが決して生成せずユーザーコードだけが返すコードも明記されています。3番・5番・6番・9番・10番・11番・15番の7つです。この7つがログに出ていればアプリケーションが意図して返したもので、それ以外は基盤側の事情が混ざっている可能性がある。切り分けの第一手として使える区別でしょう。
エラーコード全16種の一覧|番号・意味・想定する使いどころを整理
| 番号 | コード | 意味 | 受け手が取る行動 |
|---|---|---|---|
| 1 | CANCELLED | 呼び出しが取り消された | 後始末して終了 |
| 2 | UNKNOWN | 分類できない失敗 | 調査対象として記録 |
| 3 | INVALID_ARGUMENT | 引数の値が不正 | 入力を直して再送 |
| 4 | DEADLINE_EXCEEDED | 期限内に返らなかった | 期限か処理を見直す |
| 5 | NOT_FOUND | 対象が存在しない | 識別子を確認する |
| 6 | ALREADY_EXISTS | 作る対象が既にある | 既存を参照へ切替 |
| 7 | PERMISSION_DENIED | 実行の権限が足りない | 権限付与を依頼 |
| 8 | RESOURCE_EXHAUSTED | 割当や容量の枯渇 | 間隔を空けて再送 |
| 9 | FAILED_PRECONDITION | 状態が前提を満たさない | 状態を直すまで停止 |
| 10 | ABORTED | 競合などで中断した | 上位の処理から再試行 |
| 11 | OUT_OF_RANGE | 有効範囲の外を指定 | 範囲を直して再送 |
| 12 | UNIMPLEMENTED | 未実装または非対応 | 再試行せず打ち切り |
| 13 | INTERNAL | 不変条件が壊れた | 調査対象として記録 |
| 14 | UNAVAILABLE | 一時的に利用できない | 退避付きで再試行 |
| 15 | DATA_LOSS | データの喪失や破損 | 再試行せず打ち切り |
| 16 | UNAUTHENTICATED | 認証情報が無効 | 資格情報を取り直す |
入力と前提状態の誤り|3番・9番・11番のコードを分ける基準とは
取り違えが多いのがこの3つです。INVALID_ARGUMENTは、状態がどうであれ値そのものが受け付けられない場合に使います。日付の書式違いや必須フィールドの欠落が典型です。FAILED_PRECONDITIONは値は正しいがシステム側が受け入れられる状態にないことを指し、公式は状態が直るまで再試行してはならないと位置づけている。OUT_OF_RANGEは範囲外の指定に限った特殊形です。
存在と資源の誤り|5番・6番・8番で足りないものを言い分ける
NOT_FOUNDとALREADY_EXISTSは対です。取得や更新で対象が無ければ前者、作成で既にあれば後者。ここで判断が要るのは、権限が無くて見えていない対象をどちらで返すかという設計です。存在の有無そのものを秘匿したい業務では、あえてNOT_FOUNDに寄せる選択もあります。
RESOURCE_EXHAUSTEDは割当や空き容量の枯渇に使う。ライブラリ自身もメッセージサイズの上限超過でこのコードを生成するため、ログで見たら業務上のレート制限か既定4MiBの超過かをまず切り分けます。
認証と権限の誤り|16番と7番は本人確認と操作可否で分かれる
UNAUTHENTICATEDは「誰なのか確認できない」、PERMISSION_DENIEDは「誰かは分かるが操作が許されていない」を意味します。トークンの期限切れや署名検証の失敗は前者、権限不足は後者。混ぜて返すと、クライアントは再認証すべきか諦めるべきかを判断できません。
サーバー側と経路の異常|14番・13番・2番・15番の使い分けの目安
UNAVAILABLEは、いま落ちているが少し待てば戻る失敗です。停止中、接続断、keepaliveの発火など、ライブラリ側からも生成される。INTERNALは不変条件が壊れたことを示し、プロト解析や圧縮解除の失敗のように通信の前提が崩れた場面で返ります。
UNKNOWNは分類できなかった残余で、サーバー側の例外送出時の既定値でもあります。ここが多い実装は、例外をコードへ写す層が無いだけです。DATA_LOSSは喪失や破損という最も重い宣言で、使う場面を限定しないと単なる読み出し失敗にまで付いてしまいます。
HTTPステータスコードとの対応表|変換は向きによって意味が変わる
gRPCからHTTPへの対応|ゲートウェイ越しに公開するときの変換表
gRPCのサービスをHTTPとJSONで外へ出すなら、コードをHTTPステータスコードへ写す必要が出ます。googleapisのcode.protoには各コードに対応するHTTPの値がコメントで示され、ゲートウェイ実装の事実上の基準になっている。CANCELLEDだけは499という非標準の値で、経路によっては変換されずに落ちる点に注意が要ります。
| gRPCコード | HTTP | gRPCコード | HTTP |
|---|---|---|---|
| OK | 200 | ABORTED | 409 |
| CANCELLED | 499 | OUT_OF_RANGE | 400 |
| UNKNOWN | 500 | UNIMPLEMENTED | 501 |
| INVALID_ARGUMENT | 400 | INTERNAL | 500 |
| DEADLINE_EXCEEDED | 504 | UNAVAILABLE | 503 |
| NOT_FOUND | 404 | DATA_LOSS | 500 |
| ALREADY_EXISTS | 409 | UNAUTHENTICATED | 401 |
| PERMISSION_DENIED | 403 | RESOURCE_EXHAUSTED | 429 |
| FAILED_PRECONDITION | 400 | – | – |
HTTPからgRPCへの対応|grpc-statusが無い応答を受け取ったとき
逆向きの変換は別のルールで、用途も違います。grpc/grpcリポジトリのhttp-grpc-status-mapping.mdは、grpc-statusを含まない応答を受けたクライアントが使う表として、400をINTERNAL、401をUNAUTHENTICATED、403をPERMISSION_DENIED、404をUNIMPLEMENTED、429と502と503と504をUNAVAILABLE、それ以外をUNKNOWNに写すと定めます。
同ドキュメントには、この表をサーバーがHTTPコードを決める用途に使ってはならないという但し書きが付く。前段で失敗した以上、業務的な意味づけはできないという判断です。運用への影響は小さくありません。UNIMPLEMENTEDの急増が、メソッド未実装なのかプロキシの404なのかで対処はまるで違う。gRPC-Webのプロキシ構成のように途中で1枚変換が入る経路では、切り分けの手順を先に決めておくと迷いません。
対応表で迷いやすい組|400に3つ・409に2つ・500に3つが集まる
HTTP側から見ると、複数のgRPCコードが同じ番号へ潰れます。400には3番・9番・11番、409には6番と10番、500には2番・13番・15番が集まる。HTTPへ落とした時点でこの違いは失われるため、gRPCのコードを保ったまま計測する層を持つか、変換時に元のコードをログへ残すかを設計に含めておくべきです。
リトライ可否の判断|再試行してよいコードと待っても直らないコード
再試行を許すのは実質2つ|14番のUNAVAILABLEと10番のABORTED
gRPC公式のStatus codesは、再試行の扱いを3つに書き分けています。失敗した呼び出しだけを再試行できるならUNAVAILABLE。上位の階層からやり直す必要があるならABORTED。状態が明示的に直るまで再試行してはならないものがFAILED_PRECONDITIONです。
この3分類が自動再試行の対象範囲を決めます。ライブラリに任せてよいのはUNAVAILABLEだけで、ABORTEDは上位のロジックに任せます。
| コード | 自動再試行 | 方針 |
|---|---|---|
| UNAVAILABLE | 入れてよい | 指数退避とジッタで再送 |
| ABORTED | 入れない | 上位の処理単位でやり直す |
| RESOURCE_EXHAUSTED | 条件付き | 長い間隔と上限を設ける |
| DEADLINE_EXCEEDED | 入れない | 期限そのものを見直す |
| FAILED_PRECONDITION | 入れない | 状態が直るまで送らない |
| INTERNAL・UNKNOWN | 入れない | 原因不明のまま増幅させない |
retryPolicyの書き方|4つのフィールドと再試行の絞り込み設定
gRPCはサービス設定で再試行を宣言的に書けます。retryPolicyを構成するのはmaxAttempts、initialBackoff、maxBackoff、backoffMultiplier、対象を絞るretryableStatusCodesです。待ち時間には±20%のジッタが加わり、複数クライアントの再送が同じ瞬間に揃うことを避けられます。
{
"methodConfig": [{
"name": [{"service": "example.OrderService"}],
"retryPolicy": {
"maxAttempts": 4,
"initialBackoff": "0.1s",
"maxBackoff": "1s",
"backoffMultiplier": 2,
"retryableStatusCodes": ["UNAVAILABLE"]
}
}]
}
あわせて設定したいのがretryThrottlingです。maxTokensとtokenRatioでサーバー単位のトークンを増減させ、失敗が続いて半分を下回ると再試行そのものを止める。障害中のサーバーへ再送が殺到する事態をクライアント側だけで抑えられます。
DEADLINE_EXCEEDEDを再試行対象に入れてはいけない理由とは
DEADLINE_EXCEEDEDは「期限内に返らなかった」であり、「失敗した」ではありません。サーバー側は処理を続けている可能性があり、更新が完了したかをクライアントは知り得ない。自動再試行を掛けると、同じ更新が二重に走る危険が生じます。
加えて、デッドラインは呼び出し全体に対する期限です。1回目で使い切っている以上、2回目に残る時間はほとんど無く、即座に同じコードで返るだけ。対処は期限の値を見直すか、処理を分割するか、非同期の受け付け方式へ変えるかのいずれかになるでしょう。
詳細を返す設計|error detailsで機械可読な理由をクライアントへ渡す
標準モデルとリッチモデル|コードとメッセージだけでは足りない場面
標準のエラーモデルは、コードと任意の文字列メッセージだけで構成されます。gRPC公式のError handlingはこれを全言語が対応する共通の土台と位置づけている。ただし文字列は人間向けで、どのフィールドがなぜ不正なのかを機械的に読ませたい場面では足りません。そこで拡張モデルがあり、google.rpc.Statusに詳細のリストを持たせ、protobufのバイナリをトレーリングメタデータへ載せて運びます。文字列を正規表現で解析する壊れやすい実装から抜け出せます。
標準の詳細型|ErrorInfoとBadRequestとRetryInfoの使い分け
詳細として載せる型も標準セットがあります。ErrorInfoは機械が読む安定した理由コードを持ち、種別の識別に向く。BadRequestはフィールド単位の違反理由を並べ、入力検証の結果をそのまま返せます。
| 詳細の型 | 組み合わせるコード | 渡す内容 |
|---|---|---|
| ErrorInfo | 広く共通 | 安定した理由コード |
| BadRequest | INVALID_ARGUMENT | 不正なフィールドと理由 |
| PreconditionFailure | FAILED_PRECONDITION | 満たされない前提の種別 |
| QuotaFailure | RESOURCE_EXHAUSTED | 超過した割当の名前 |
| RetryInfo | UNAVAILABLEなど | 再試行してよい時刻 |
| LocalizedMessage | 利用者へ見せる場合 | 言語別の説明文 |
勘所は、コードは粗く保ち、細かい区別をErrorInfoの理由コードへ寄せることです。16種は業務都合で拡張できないため、固有の区別を持ち込むと意味が濁る。分岐の骨格はコード、表示や個別対応は詳細で分けます。なお詳細はトレーラに載るためスタックトレースを詰めるとヘッダ上限に当たり、公式が実装例を示すのはC++・Go・Java・Python・Rubyで言語差がある点は前提に置いてください。
クライアント実装の分岐設計|コードで分けて詳細で理由を説明する
分岐は3つに畳む|再試行する・入力を直す・その場で打ち切るの3択
16種すべてに個別の処理を書く実装は、書いた本人にしか保守できません。呼び出し側が取れる行動は、再試行する・直して送り直す・上位へ伝えるの3つだけです。この3群へ写す関数を1つ用意し、個々の呼び出しはその結果だけを見るようにします。
| 分岐 | 該当コード | 実装での扱い |
|---|---|---|
| 再試行する | UNAVAILABLE | 退避付きで自動再送 |
| やり直す | ABORTED | 業務処理の単位で再構成 |
| 入力を直す | 3番・11番・16番 | 利用者へ差し戻す |
| 状態を直す | FAILED_PRECONDITION | 前提を整えてから再送 |
| 打ち切る | 7番・12番・15番 | 再送せず上位へ伝える |
| 記録する | UNKNOWN・INTERNAL | 調査待ちとして通知 |
一元化と観測|インターセプタで記録し監視の軸をコード別に置く
再試行、ログ出力、メトリクスの記録は、呼び出しごとに書くと必ずどこかで抜けます。クライアントインターセプタで一度だけ実装し全メソッドへ適用すると漏れがない。自動再試行はretryPolicyに任せられるため、インターセプタ側は記録と写像に専念させるのが素直です。ストリーミングでは終了コードが最後のメッセージを読み切った時点で確定するため、unaryと同じ書き方では途中まで受信済みのデータの扱いが宙に浮きます。方式ごとの受け取り方はgRPCストリーミングの4方式と切断対策で整理しています。
運用で最初に効くのは、メソッド名とコードの2軸でカウンタを持つことです。HTTP側の200しか見ていない監視では、この情報は最後まで得られない。UNKNOWNとINTERNALの比率は実装の健全さの指標になり、この2つが目立って増えるなら例外をコードへ写す層が抜けている疑いが濃いでしょう。
採用の境界|サーバー側で返してよいコードを許可リストで絞る判断
許可リストで運用する3条件|使うコードを先に決めて実装を縛る
16種すべてを使える状態にすると、実装者ごとに割り当てがばらつきます。返してよいコードを先に決めレビューで弾く運用にすると、クライアント側の分岐が安定する。採ってよいのは次の3条件が揃うときです。
ひとつ、サーバーとクライアントの両方を自社で設計できること。ふたつ、公開範囲が社内システム間か、契約で仕様を握れる相手に限られること。みっつ、種別の細分化をErrorInfoの理由コードで吸収できること。揃うなら、業務APIで使うコードは3番・5番・6番・7番・8番・9番・16番の7つまで絞れます。
逆に、不特定多数の外部開発者へ公開するAPIや、既存のHTTP APIと並走する移行期には向きません。相手の実装が想定外のコードを前提にしている場合があるためです。
見送る使い方|UNKNOWNの放置とINTERNALの濫用が招く運用負債
やめたほうがよい使い方も条件で言い切れます。第一に、例外をそのまま流してUNKNOWNにする運用。原因が分類されないまま蓄積し、監視でも切り分けられない。例外の種類をコードへ写すハンドラを1枚挟むだけで解消します。
第二に、迷ったらINTERNALという運用です。不変条件が壊れたという強い宣言で、本来は緊急度の高い通知に結び付けるもの。日常的な入力エラーまで含めると通知が鳴り続けます。第三に、ABORTEDやDATA_LOSSを再試行対象へ入れる設定。前者は同じ競合を繰り返すだけです。
受託開発での取り決め|APIエラー仕様を契約前に合意しておく項目
複数社でシステムを分担する案件では、エラーの取り決めが抜けたまま結合試験に入りがちです。正常系のスキーマは.protoで共有されるのに、失敗時の表現は各社の実装任せになる。片方はNOT_FOUNDを想定し、もう片方は空応答を返す食い違いが終盤で出てきます。
合意しておきたいのは4項目です。返すコードの許可リスト。理由コードの命名と管理者。再試行してよいコードと待ち時間の下限。詳細を読めないクライアントが混ざる場合の縮退動作。この4つを設計書に書けば、後から参加する実装者も同じ判断ができます。エラー仕様を含むAPI設計や既存システムとの接続については、API開発・システム連携で相談を受け付けています。
よくある質問
gRPCのステータスコードは16種と17種のどちらが正しいですか?
どちらも同じものを指しています。公式が定義するのは0番のOKから16番までの17個で、成功を表すOKを除いた16個がエラーコードです。エラーハンドリングの文脈では16種、定義の総数では17個と読み替えれば齟齬はありません。
独自のエラーコードを追加することはできますか?
コード自体を増やすことはできません。grpc-statusの値は仕様で決まっており、範囲外の値を返しても受け側が解釈できないためです。業務固有の区別が要るなら、コードは既存の16種から選び、ErrorInfoの理由コードで細分化します。
ストリーミングの途中でエラーを返すとコードはどう届きますか?
ストリームの終了時にトレーラとして届きます。それまでに送ったメッセージはすでにクライアントへ渡っており、途中まで処理が進んだ状態でコードだけが失敗を告げる。どこまで処理済みかを伝えるなら、メッセージに通し番号を持たせる設計が要ります。
DEADLINE_EXCEEDEDとUNAVAILABLEはどう見分けますか?
前者は期限切れ、後者は接続できないか一時的に応答できない状態です。処理が長引いたならDEADLINE_EXCEEDED、接続が確立しない、停止中、keepaliveが切れたならUNAVAILABLE。自動再試行してよいのは後者だけと覚えておくと判断を誤りません。
エラーの内容をログにどこまで残すべきですか?
コード、理由コード、追跡用の識別子の3点は必須と考えてよいでしょう。リクエスト本文をそのまま残す運用は個人情報が混ざるため避けます。grpc-messageにも内部構造を書かず、詳しい情報は識別子でサーバー側のログに紐づけます。
関連記事
- gRPCとは?仕組み・RESTとの違い・UDP/TCPの扱いを実装例つきで解説:プロトコルの基礎と通信の成り立ち
- HTTPステータスコード一覧とcurlエラーコード一覧|違い・確認方法:HTTP側の番号の意味と確認方法
- gRPCストリーミングとは?4方式の使い分けと切断対策を実装者向けに解説:4方式の選び分けと切断対策
- gRPC-Webとは?ブラウザからgRPCを呼ぶ仕組みとプロキシ構成・採用判断を解説:ブラウザから呼ぶ経路と制約
- Connectとは?gRPC・RESTとの違いと使い方【connectrpc】:プロトコルの比較軸