
2026/02/09 21:17
**gRPC:サービス定義から通信フォーマットへ**
RSS: https://news.ycombinator.com/rss
要約▶
Japanese Translation:
Summary
gRPCはHTTP/2上で動作する高性能なリモートプロシージャコール(RPC)フレームワークで、各RPC呼び出しごとに単一のストリームを使用して効率的なマルチプレクシングを実現します。契約ファーストモデルに従い、サービスとメッセージは
.protoファイルで定義され、protocコンパイラが言語固有のクライアントおよびサーバスタブ(Go、Java、C#、Pythonなど)を生成します。gRPCはユニリック、サーバーストリーミング、クライアントストリーミング、および双方向ストリーミングという4つのストリーミングモデルをサポートし、柔軟な通信パターンを可能にします。
メタデータはHTTP/2ヘッダーとして送信されます:キーは大文字小文字を区別せず、
grpc-で始まることはできません。また、バイナリ値は必ず-binで終わらなければなりません。これらのヘッダーは認証トークン、トレーシングID、ロードバランサーやプロキシへのインフラヒントなど、横断的な関心事を運搬することができます。gRPCのURLパスはproto定義から自動的に導出されます(/{Package}.{Service}/{Method})。
各ワイヤーメッセージは5バイトのヘッダーでフレーム化されます(byte 0 = 圧縮フラグ、bytes 1–4 = ビッグエンディアンのペイロード長)。アプリケーションのステータスコードはトレーラー(
grpc-status、grpc-message)で送信され、HTTPステータスコードではありません。トレーラーにはgoogle.rpc.Status protobufを介して豊富なエラーディテールを含めることができます。圧縮交渉はgrpc-accept-encoding(クライアント)とgrpc-encoding(サーバ)を使用し、各メッセージの圧縮フラグがペイロードが圧縮されているかどうかを示します。
gRPCはTCP/IPだけでなく、Unix Domain SocketsやNamed Pipesなどの代替トランスポート上でも動作可能です。ブラウザはネイティブgRPCを扱えません(HTTP/2トレーラーサポートがないため)ので、gRPC‑Webプロトコルはトレーラーをデータストリームに埋め込み、base64エンコードを使用してこの制限を回避します。
本文
前回の投稿(パート 1 と パート 2)では
Protocol Buffers を解明し、データがどのようにコンパクトなバイナリへエンコードされるかを学びました。
しかし Protobuf はあくまでペイロードです。マイクロサービス間でこのデータを送受信するには
トランスポートプロトコル ― gRPC が必要になります。
多くの開発者は gRPC を日常的に使っていますが、実際にどのように動作しているかを
裏側まで眺める人は少数です。この記事では基本的な部分を越えて
gRPC のプロトコルスタック全体(上位のサービスアーキテクチャ・ストリーミングモデルから
低レベルの HTTP/2 フレームとバイト単位のワイヤフォーマットまで)を探っていきます。
コントラクトファースト哲学
gRPC の中心にあるのは「コントラクトファースト」です。REST では API ドキュメント(例:OpenAPI)が 後付けになることが多いですが、gRPC は Protocol Buffers (
.proto ファイル) を使って
構造を最初から強制します。
package fruit.v1; service FruitService { // Unary: 単純なリクエスト → レスポンス rpc GetFruit(GetFruitRequest) returns (Fruit); // Server streaming: 一つのリクエスト → 複数のレスポンス rpc ListFruits(ListFruitsRequest) returns (stream Fruit); // Client streaming: 多数のリクエスト → 一つのレスポンス rpc Upload(stream Fruit) returns (UploadSummary); // Bidirectional streaming: 双方向に多数のメッセージをやり取り rpc Chat(stream ChatMessage) returns (stream ChatMessage); }
この定義が真実の源です。1 つのファイルから
protoc がクライアントスタブとサーバー用の雛形をほぼすべての言語(Go、Java、C#、Python 等)で生成し、クライアントとサーバーが常に API の 構造を合意できるようにします。
ストリーミングモデル
gRPC がネイティブにサポートするストリーミングは重要な差別化要因です。
「チャンク転送エンコーディング」ではなく、ファーストクラスの API セマンティクスとして扱われます。
| モデル | 説明 |
|---|---|
| Unary | 通常の関数呼び出しや REST リクエストに似ています。クライアントは 1 メッセージを送信し、サーバーは 1 で応答します。 |
| Server streaming | サブスクリプションや大規模データセットに最適です。クライアントがクエリを送り、サーバーは時間とともに複数の結果を返します。 |
| Client streaming | IoT デバイスからのテレメトリーなど、データをストリームで送る際に有効です。サーバーは受信したメッセージを逐次処理します。 |
| Bidirectional streaming | 本当のリアルタイム通信です。両側が独立してメッセージを送れます(チャットアプリやマルチプレイヤーゲームで使用)。 |
実際のデータに加えて、gRPC はメタデータ(HTTP ヘッダーに似たキー–バリューのリスト)も送信できます。
キーは大文字小文字を区別せず、
grpc- で始まるもの(gRPC 内部用)は予約済みです。バイナリ値は
-bin で終わります。メタデータは認証・トレーシング・インフラのヒントなど、横断的な関心事に不可欠です。
- 認証 – 例:
Authorization: Bearer <token> - トレーシング – 例:
transport-id: 12345 - インフラのヒント – 例:ロードバランサやプロキシへの指示
メタデータはクライアント側(呼び出し開始時)とサーバー側(ヘッダーとして開始時、トレーラーとして終了時)の両方から送信可能です。
裏側:トランスポート層
gRPC は HTTP/2 の上に構築されており、その高度な機能を利用してストリーミングを実現します。
- ストリーム – すべての gRPC 呼び出しは単一の HTTP/2 ストリームにマップされます。これにより、1 本の TCP 接続で数千のアクティブ呼び出しを複合でき、HTTP/1.1 のヘッド・オブ・ライン問題を回避します。
- URL 構築 – パスは
定義から自動生成されます:.proto
。/{Package}.{Service}/{Method}
例:
の場合、URL はGetFruit
になります。/fruit.v1.FruitService/GetFruit
HTTP/2 フレームと gRPC 呼び出し
典型的な gRPC 呼び出しは 3 段階で構成され、それぞれが HTTP/2 フレームに対応します:
- リクエストヘッダーとメタデータ(HEADERS フレーム):
、:path
(:method
) とPOST
を含みます。content-type: application/grpc - データメッセージ(DATA フレーム):実際の protobuf ペイロードです。
- レスポンストレーラー(HEADERS フレーム):呼び出しの最終状態を示します。
メタデータは HTTP/2 ヘッダーとして送信され、文字列値はそのまま、バイナリ値は
-bin で終わるキーとともに Base64 エンコードされます。
長さ付きフレーミング
各 HTTP/2 DATA フレーム内で、gRPC は protobuf メッセージを 5 バイトのヘッダーでラップします:
| バイト | 用途 |
|---|---|
| 0 | 圧縮フラグ ( = 未圧縮、 = 圧縮) |
| 1–4 | メッセージ長(ビッグエンディアンの 32 ビット整数) |
例 – 前述の
Fruit メッセージを送る場合:
- Protobuf ペイロード:
(10 バイト)08 96 01 12 05 41 70 70 6c 65 - ヘッダー:
00 00 00 00 0a - 最終 gRPC フレーム:
00 00 00 00 0a 08 96 01 12 05 41 70 70 6c 65
ヘッダーにより、受信側は各メッセージの正確なバイト数を読み取れるため、ストリーミングがスムーズになります。
ステータスとトレーラー
REST(HTTP ステータスコード)とは異なり、gRPC は常に HTTP 200 OK を返します。実際のアプリケーションステータスは最後の HTTP/2 トレーラーフレームで送られます:
grpc-status: 0 grpc-message: OK
grpc-status がゼロ以外の場合、NOT_FOUND や UNAVAILABLE 等のエラーを表します。
より詳細なエラー情報を提供するために、gRPC は protobuf メッセージ(
google.rpc.Status)を Base64 でエンコードした grpc-status-details-bin トレーラを送信できます。このメッセージは構造化されたアクショナブルなエラー情報を含みます。
message Status { int32 code = 1; // 例:3=INVALID_ARGUMENT string message = 2; // 開発者向けエラーテキスト repeated google.protobuf.Any details = 3; }
クライアントはこのトレーラーをデコードして、構造化されたエラー情報を取得できます。
圧縮
モバイルネットワークなどで帯域幅を節約するために圧縮が有効です。
- ネゴシエーション – クライアントは
を送ります。grpc-accept-encoding: br,gzip,identity - エンコーディング選択 – サーバーは
(例)で応答します。grpc-encoding: br - メッセージごとのフラグ – 5 バイトヘッダーのバイト 0 が
に設定されます。1 - ペイロード – 選択したアルゴリズムで圧縮されたデータです。
例:Brotli 圧縮された “Apple” ペイロード
01 00 00 00 0e <圧縮バイト列>
構造は変わらず、フラグと長さだけが異なります。
他のトランスポート
gRPC は通常 TCP/IP + HTTP/2 上で動作しますが、他のトランスポートでも動かせます:
- Unix ドメインソケット – ローカル IPC に最適でネットワークスタックをバイパスします。
- Named Pipes – Windows 版 Unix ソケット。
この柔軟性により、gRPC はコンポーネント間のユニバーサルな接続手段として機能し、世界中どこにいても、同じチップ上でも使用できます。
ブラウザギャップ(gRPC‑Web)
ブラウザは標準 gRPC に必要な低レベル HTTP/2 フレーミング制御(例:トレーラーの読み取り)ができません。gRPC‑Web はプロトコルを適応させます:
- トレーラーをデータストリーム本文内にエンコードし、ブラウザが HTTP トレーラーを読む必要がなくなります。
- テキストベース(Base64)エンコーディングをサポートしてバイナリ制約を回避します。
gRPC‑Web については次回の投稿で詳しく解説します。
締めくくりの考え方
gRPC は単なるシリアライズフォーマットではなく、API を定義・生成・消費する方法を標準化した完全なエコシステムです。
.proto コントラクトからワイヤ上の 5 バイトヘッダーまでを理解すれば、問題解決が効率的になり、より良い設計が可能になります。Kreya のようなツールはこの複雑さを抽象化しますが、裏側で何が起きているかを知っておくと、困難に直面した際に自ら解決策を選べます。
さらに読む
- gRPC ベストプラクティス – API 設計・バージョニング・パフォーマンスヒント
- 公式 gRPC ドキュメント – コアコンセプト、アーキテクチャ、ライフサイクル
- gRPC HTTP/2 トランスポート仕様書 – gRPC 用の公式 HTTP/2 スペック
- Protocol Buffers(パート 1 & 2) – Protocol Buffers フォーマットの詳細解析