1.

Protocol Buffers(protobuf)とは|仕組み・.protoとJSONとの違い

編集
この記事の要点
  • Protocol Buffers(protobuf)は、Googleが開発した、構造化データを効率的にシリアライズするためのスキーマ定義とバイナリ形式です。
  • データ構造を.protoファイルでスキーマとして定義し、protocコンパイラで各プログラミング言語向けのコードを生成して利用します。
  • テキスト形式のJSONやXMLと比べ、一般にデータサイズが小さく、エンコード/デコードが高速になりやすい一方、生成されるデータは人が直接読みにくいという特徴があります。
  • gRPCのインターフェース定義言語(IDL)として広く使われ、サービスやメッセージの定義に用いられます。
  • 各フィールドに割り当てる「フィールド番号」が後方互換の鍵で、一度使った番号の意味を変えたり再利用したりしない運用が重要です。

Protocol Buffers(protobuf)とは

Protocol Buffers(プロトコル バッファーズ、protobuf)は、Googleが開発した、構造化データを効率的にシリアライズするためのスキーマ定義とバイナリ形式です。やり取りしたいデータの構造をあらかじめスキーマとして定義し、その定義に基づいてコンパクトなバイナリへ変換(シリアライズ)したり、元のデータ構造へ復元(デシリアライズ)したりする仕組みを提供します。

「シリアライズ」とは、メモリ上のオブジェクトやデータ構造を、ネットワーク送信やファイル保存に適した一連のバイト列へ変換することを指します。protobufはこのシリアライズの方式と、データ構造を記述するスキーマ言語、そしてスキーマからコードを生成するツール群をまとめて提供している点が特徴です。

主な用途は、サービス間の通信メッセージ、データの永続化フォーマット、設定やログのやり取りなど、効率と相互運用性が求められる場面です。スキーマと生成コードを介するため、異なるプログラミング言語で書かれたプログラム同士でも、同じデータ構造を共有して通信できます。後述するgRPCでは、このprotobufが通信メッセージの定義に標準的に用いられています。

protobufの基本的な流れ

protobufの利用は、おおまかに次の3ステップで進みます。

  1. スキーマを定義する: データ構造を.protoという拡張子のファイルに記述します。どんな項目(フィールド)があり、それぞれがどの型かを宣言します。
  2. コードを生成する: protocと呼ばれるコンパイラに.protoファイルを渡し、利用したい言語向けのソースコード(クラスや構造体、シリアライズ用の処理など)を生成します。
  3. 生成コードを使う: 生成されたコードをアプリケーションに取り込み、データのシリアライズ/デシリアライズを行います。

スキーマという共通の「契約」を中心に置くことで、送信側と受信側が別々の言語であっても、同じ約束に従ってデータを解釈できるようになります。

.protoファイルでスキーマを定義する

protobufでは、まず.protoファイルにデータ構造を記述します。データのまとまりはmessageという単位で定義し、その中に個々のフィールドを並べます。各フィールドには「型」「名前」「フィールド番号」を与えます。

syntax = "proto3";

message User {

  int32 id = 1;

  string name = 2;

  bool is_active = 3;

  repeated string roles = 4;

}

この例ではUserというメッセージを定義しています。各行の意味は次のとおりです。

  • syntax = "proto3";: 使用する構文のバージョン宣言です。現在広く使われている構文としてproto3があり、これに加えて従来からのproto2も存在します。
  • int32 id = 1;: idという名前の整数フィールドで、フィールド番号は1です。末尾の数字は値そのものではなく、後述する「フィールド番号」を表します。
  • string name = 2;: 文字列型のフィールドです。
  • bool is_active = 3;: 真偽値型のフィールドです。
  • repeated string roles = 4;: repeatedを付けると、同じ型の値を複数(リスト・配列)持てるフィールドになります。

主なスカラー型には、整数を表すint32int64、文字列のstring、真偽値のbool、浮動小数点のfloatdouble、バイト列のbytesなどがあります。さらに、メッセージを入れ子にしたり、列挙型enumを定義したりすることもできます。

フィールド番号の役割

各フィールドに付与するフィールド番号は、protobufの中核的な概念です。バイナリにエンコードされる際、フィールド名そのものではなく、このフィールド番号が識別子として書き込まれます。そのため、フィールド名はあくまで人間と生成コードのためのものであり、データの実体ではフィールド番号によって項目が区別されます。この設計が、後述する後方互換性とサイズ効率の両方を支えています。

protocでコードを生成する

定義した.protoファイルは、protoc(Protocol Buffersコンパイラ)に渡すことで、各言語向けのソースコードに変換されます。生成されたコードには、メッセージを表すクラスや構造体、フィールドへのアクセス手段、シリアライズ/デシリアライズの処理などが含まれます。

protocは多くのプログラミング言語に対応しており、言語ごとにプラグインや出力オプションを指定して生成します。たとえばGo向けのコードを生成する場合、概念的には次のような形でコンパイラを呼び出します(実際のオプションやプラグインの導入方法は環境や利用するツールによって異なります)。

protoc --go_out=. user.proto

このように、同じ.protoファイルから複数の言語向けコードを生成できるため、サーバーとクライアントが別の言語で書かれていても、共通のスキーマに基づいて通信できます。スキーマを一元管理し、そこから各言語のコードを自動生成する流れが、protobufを使った開発の基本スタイルです。

JSON/XMLとの比較

protobufはバイナリ形式である点が、テキストベースのJSONやXMLと大きく異なります。それぞれに向き不向きがあるため、用途に応じて選択します。一般的な傾向を整理すると次のようになります(具体的な差は、データ内容や実装、計測条件によって変動します)。

観点 Protocol Buffers JSON XML
データサイズ 小さくなりやすい(バイナリ) 中程度(テキスト) 大きくなりやすい(タグが冗長)
処理速度 高速になりやすい 中程度 比較的遅くなりやすい
人による可読性 低い(そのままでは読めない) 高い 高い
スキーマ 必須(.protoで定義) 任意(スキーマなしでも可) 任意(XML Schema等で付与可能)
主な用途の例 サービス間通信・永続化 Web API・設定ファイル 文書・設定・既存システム連携

JSONやXMLは人が読み書きしやすく、スキーマなしでも扱えるため、汎用的なWeb APIや設定ファイルで広く使われています。一方protobufは、スキーマを前提とする代わりに、サイズと処理効率の面で有利になりやすく、サービス間の大量・高頻度な通信や、効率を重視するデータ保存に適しています。どちらが優れているということではなく、可読性・スキーマの厳密さ・効率のどれを重視するかで選び分けるものと考えるとよいでしょう。

gRPCとの関係

protobufは、Googleが開発したRPC(リモートプロシージャコール)フレームワークであるgRPCと組み合わせて使われることが多い技術です。gRPCでは、サービスのインターフェース定義言語(IDL)としてprotobufが標準的に用いられ、やり取りするメッセージの構造に加えて、呼び出せるメソッド(サービス)も.protoファイルに記述します。

service UserService {

  rpc GetUser (GetUserRequest) returns (User);

}

message GetUserRequest {

  int32 id = 1;

}

この例では、UserServiceというサービスにGetUserというメソッドを定義し、リクエストとしてGetUserRequestを受け取り、レスポンスとして先ほどのUserメッセージを返すことを宣言しています。protocとgRPC用のプラグインによって、このサービス定義からサーバー側・クライアント側それぞれのコードが生成され、開発者はメソッド呼び出しの形でリモート通信を扱えるようになります。

ただし、protobufは必ずしもgRPCと一緒に使う必要はありません。スキーマ定義とシリアライズの仕組みとして単独でも利用でき、独自の通信やデータ保存に組み込むこともできます。一方でgRPCの側からは、protobufが事実上の標準的なメッセージ形式として位置づけられています。

後方互換性とフィールド番号

protobufが実用面で重視される理由の一つが、スキーマを進化させやすい後方互換性です。サービスを運用していると、後からフィールドを追加したくなることがよくあります。protobufでは、既存のフィールド番号を変えずに新しいフィールドを新しい番号で追加する限り、互換性を保ちやすい設計になっています。

これは、バイナリ上でフィールドを識別しているのがフィールド名ではなくフィールド番号だからです。新しい番号のフィールドを追加しても、その番号を知らない古い側はそのデータ部分を未知のものとして扱い、既存フィールドの解釈には影響しません。逆に、新しい側が古いデータを読む場合は、まだ存在しないフィールドが欠けているだけとして扱えます。このため、送信側と受信側のスキーマのバージョンに多少のずれがあっても、通信を継続しやすくなります。

互換性を保つうえでの基本的な指針は次のとおりです。

  • 一度割り当てたフィールド番号は変更しない。番号を変えると、古いデータとの対応が崩れます。
  • フィールドを追加するときは、未使用の新しい番号を割り当てる。
  • 不要になったフィールドを削除する場合でも、その番号を別の用途に流用しない

落とし穴・注意点

注意点 内容
フィールド番号の再利用 削除したフィールドの番号を別のフィールドに使い回すと、古いデータが新しい定義で誤って解釈される恐れがあります。番号は意味とともに固定し、再利用しない運用が重要です。不要になった番号は予約しておく方法もあります。
スキーマの管理 .protoファイルが事実上の「契約」になるため、複数のサービスやチームで共有する際は、バージョン管理や変更レビューの仕組みを整えないと、互換性の崩れに気づきにくくなります。
人が直接読めない シリアライズ結果はバイナリのため、ログやネットワークを目視で確認したいデバッグ用途には不向きです。確認にはスキーマと専用ツールが前提になります。
スキーマ前提の運用コスト スキーマ定義とコード生成というステップが加わるため、スキーマなしで手軽に扱えるJSONなどと比べ、小規模・単発の用途では準備の手間が相対的に大きく感じられる場合があります。

よくある質問(FAQ)

Q. protobufを使うには必ずgRPCが必要ですか?

いいえ。protobufはスキーマ定義とシリアライズの仕組みであり、単独でも利用できます。データの保存形式や独自の通信のメッセージ形式として使うことも可能です。gRPCは、そのprotobufを標準のメッセージ形式として採用しているフレームワークの一つという関係です。

Q. JSONをやめてprotobufに置き換えるべきですか?

一概には言えません。可読性やスキーマなしの手軽さが重要な場面ではJSONが適しており、サイズや処理効率、スキーマによる厳密さを重視する場面ではprotobufが向いています。求める要件によって使い分けるのが現実的です。両者を場面ごとに併用することも珍しくありません。

Q. 後から新しいフィールドを追加しても大丈夫ですか?

新しいフィールドを未使用のフィールド番号で追加し、既存のフィールド番号を変更しない限り、互換性を保ちやすい設計になっています。逆に、既存番号の変更や、削除した番号の使い回しは互換性を損なう原因になるため避けてください。

編集
Post Share
子ページ

子ページはありません

同階層のページ
  1. Protocol Buffers
  2. 詳細説明付きクイックスタート
  3. Docker + Go言語 + gRPC で簡単なWebアプリケーションを作る その1

最近更新/作成されたページ