Protocol Buffers

Google が開発した効率的なバイナリシリアライゼーションフォーマットで gRPC の標準データ形式

データ形式RPC

Protocol Buffers とは

Protocol Buffers (protobuf) は、Google が 2008 年にオープンソース化した言語中立・プラットフォーム中立のバイナリシリアライゼーションフォーマットである。.proto ファイルでスキーマを定義し、コンパイラ (protoc) が各言語のシリアライゼーション/デシリアライゼーションコードを自動生成する。gRPC のデフォルトデータ形式として広く使われている。

スキーマ定義

スキーマ定義の例を示す。

syntax = "proto3";

package order;

import "google/protobuf/timestamp.proto";

message Order {
  string id = 1;              // フィールド番号で識別
  string user_id = 2;
  repeated Item items = 3;    // 配列
  OrderStatus status = 4;
  google.protobuf.Timestamp created_at = 5;
}

message Item {
  string product_id = 1;
  int32 quantity = 2;
  int64 price_cents = 3;      // 金額はセント単位の整数
}

enum OrderStatus {
  ORDER_STATUS_UNSPECIFIED = 0;
  ORDER_STATUS_PENDING = 1;
  ORDER_STATUS_CONFIRMED = 2;
  ORDER_STATUS_SHIPPED = 3;
}

// gRPC サービス定義
service OrderService {
  rpc GetOrder(GetOrderRequest) returns (Order);
  rpc CreateOrder(CreateOrderRequest) returns (Order);
  rpc ListOrders(ListOrdersRequest) returns (stream Order); // サーバーストリーミング
}

フィールド番号 (= 1, = 2) はバイナリエンコーディングで使われる識別子だ。バイナリ形式はフィールド名を持ち回らないため、名前を変えても既存のバイナリデータはそのまま読める。ただし ProtoJSON やテキスト形式ではフィールド名がそのまま直列化されるので、それらの形式でデータを保存・交換している場合は、名前の変更が互換性を壊す変更になる。

proto3 の列挙型は先頭に 0 の値を置くことが必須で、その 0 が既定値になる (例の ORDER_STATUS_UNSPECIFIED)。同じように、明示的な presence を持たないスカラーフィールドは、パースし終えた時点で「0 や false が明示的に入っていた」のか「そもそも送られてこなかった」のかを区別できない。false のときだけ挙動を切り替えるフラグや、0 と未指定を分けたい数量では、optional を付けて presence を持たせる。

JSON との比較

JSON との主な違いを以下に比較する。

観点Protocol BuffersJSON
データサイズフィールド名を運ばないぶん小さい人間が読めるが冗長
パース速度スキーマが既知のため速いパースにコストがかかる
スキーマ必須 (.proto)任意 (JSON Schema)
可読性バイナリで人間が読めないテキストで読める
後方互換性フィールド番号を軸に維持フィールド名に依存
コード生成自動 (protoc)手動 or ツール
ブラウザ対応grpc-web が必要ネイティブ対応

サイズと速度の差がどれだけ出るかはスキーマとデータの中身次第だ。長い文字列が主体のメッセージでは差が縮み、数値や列挙型が並ぶメッセージでは大きく開く。移行の判断材料にするなら自分のデータで測る。

スキーマの進化 (後方互換性)

Protocol Buffers の最大の強みは、スキーマの安全な進化だ。

// v1: 初期スキーマ
message User {
  string id = 1;
  string name = 2;
}

// v2: フィールドを追加 (後方互換)
message User {
  string id = 1;
  string name = 2;
  string email = 3;        // 新フィールド: 古いクライアントは無視
  repeated string tags = 4; // 新フィールド: 古いクライアントは無視
}

ルール:

  • フィールドの追加は安全 (古いクライアントは未知のフィールドを無視)
  • フィールド番号は再利用しない。削除したフィールドは番号とフィールド名の両方を reserved で予約する (名前を予約しないと、ProtoJSON やテキスト形式で書かれた既存データのパースが壊れる)
  • 型変更は組み合わせ次第で、一律に非互換ではない。int32 uint32 int64 uint64 bool は相互に互換、stringbytes も中身が妥当な UTF-8 である限り互換だ。逆に sint32 sint64 は他の整数型と互換にならず、fixed32sfixed32 と、fixed64sfixed64 としか互換にならない。互換な組み合わせに載らない変更をしたいときは、新しいフィールド番号を起こす

マイクロサービスでの活用

Protocol Buffers + gRPC は、マイクロサービス間の通信に最適だ。.proto ファイルがサービス間の契約 (Contract) として機能し、サーバーとクライアントのコードが自動生成される。JSON + REST に比べて、型安全性が高く、通信効率が良い。

現場での応用を知るには関連書籍も役立つ。

この記事は役に立ちましたか?

関連用語

関連する記事