プロトコル

Protocol Buffersとは?protoファイルの書き方と採用判断を実装者向けに解説

Protocol Buffersは、Googleが公開した構造化データの直列化形式です。同時に、.protoという定義ファイルにスキーマを書くIDL(インターフェース定義言語)でもあり、この二面性が用途の広さを生んでいます。この記事では、messageとフィールド番号の付け方、protocでコードを生成する手順、タグとvarintが決めるバイト数の内訳、後方互換を壊さないスキーマ進化の規則、gRPC以外での単体利用、そしてproto3とEditionsのどちらで新規開発すべきかまでを実装視点で整理しました。採用を見送るべき場面も条件付きで示します。

まとめ|Protocol Buffersが効く境界とproto3から先の選び方

先に結論を置きます。Protocol Buffersが効くのは、通信の両端を自社が握っている系統です。マイクロサービス間、モバイルアプリと自社バックエンド、社内のデータ基盤。ここではスキーマを共有できるので、バイナリの小ささと型の強制が素直に利きます。

逆に、不特定多数の開発者が叩く公開APIには向きません。人が目で読めない、curlで試せない、クライアント側に生成コードの導入を強いる。この3点が採用障壁になり、JSONを返すほうが結局は速く回ります。

新規に書き始めるなら、2026年8月時点ではEditions(edition = "2024")を第一候補にしてください。ただし条件があります。protoc 32.0以降と、対応済みの言語ランタイムが揃っていること。既存のproto3資産が大きい現場では、無理に一斉移行せず新規ファイルから順に切り替える運用で十分です。

そして、最初に決めるべきはフィールド番号の運用規約です。番号を一度でも再利用すると、古いバイナリが送った値が別の型のフィールドとして読まれ、データが静かに壊れます。削除は必ずreservedとセットにしてください。

Protocol Buffersの定義とJSON・XMLとの違い|バイナリ形式の効き方

まず、この形式が何を担当していて何を担当していないのかを切り分けます。

Googleが公開したIDLとシリアライズ形式という二つの役割

Protocol Buffersが引き受けるのは、構造化データを小さなバイト列に変換すること(直列化)と、その構造を言語非依存の形で宣言すること(IDL)の二つです。通信路そのものは持ちません。HTTP/2に乗せるのか、Kafkaのメッセージ本体に置くのか、ファイルに保存するのかは利用側が決めます。

この分業を押さえると、gRPCとの関係も整理できます。gRPCは通信の枠組みで、Protocol Buffersはその上を流れるデータとサービス定義の記法。片方だけを使うこともできます。

JSONと比べたバイト数と解析速度の差|バイナリが効く条件と効かない条件

差が出る理由は単純です。JSONはフィールド名を毎回テキストで送りますが、Protocol Buffersは番号1つ(多くは1バイト)に置き換えます。フィールド名が長く、レコード数が多いほど差が開きます。

一方で、条件次第では差が縮みます。ペイロードが数百バイト程度、通信が数分に1回、経路でgzipが効いている。この条件が揃うと、体感できる改善は生まれにくくなります。導入判断は「小さくなるか」ではなく「何回流れるか」で考えてください。

XML・MessagePack・Avroとの比較|スキーマ管理コストの違い

バイナリ形式は他にもあります。選定軸は速度より、スキーマをどこで管理するかです。

形式 スキーマ 形式 主な使いどころ
Protocol Buffers 必須(.proto) バイナリ サービス間通信・gRPC
JSON 任意 テキスト 公開API・設定ファイル
XML XSDで任意 テキスト 文書・旧来の企業間連携
MessagePack 不要 バイナリ JSONの軽量な置き換え
Avro 必須(JSON定義) バイナリ データ基盤・Kafka

MessagePackはスキーマが要らない代わりに型の保証も互換性検査もありません。Avroはスキーマをデータに同梱するか外部レジストリで配る設計で、データ基盤寄りです。Protocol Buffersはスキーマを事前共有するぶん、生成コードによる型安全が最も強く出ます。

.protoファイルの書き方とprotocによるコード生成の手順

実装は3ステップです。定義を書き、コードを生成し、生成物を使う。

message定義とフィールド番号の付け方|1から15を先に使う理由

データ構造はmessageで宣言し、各フィールドに型・名前・番号を与えます。番号は1から536,870,911の範囲で、19,000から19,999は実装が予約しているため使えません。

設計上効くのは番号の割り当て順です。1から15はタグが1バイトに収まり、16から2047は2バイトになります。頻繁に出現するフィールドやrepeatedの要素に若い番号を割り当てておくと、レコード数が増えたときの差が明確です。埋め合わせの余地を残したいなら、将来の頻出候補として1桁台を数個空けておく手もあります。

protocとbuf 1.72系によるコード生成の手順と生成物の置き場所

生成はprotocにプラグインを組み合わせて行います。Go・Java・Python・C#など言語ごとにプラグインが分かれており、出力先ディレクトリとimport解決のパスを指定します。

ただし実務では、protocを直接叩く構成は壊れやすくなります。プラグインの版と出力オプションが人ごとに揃わないためです。buf(2026年8月時点でCLI v1.72.0)のように設定ファイルで生成条件を固定するツールを挟むと、CIとローカルの生成結果が一致します。生成物の置き場所は、リポジトリに含めるかビルド時生成にするかで運用が変わるため、後述の導入コストの節で判断基準を示します。

service定義がgRPCへ渡す入出力|メッセージとRPCの分担

serviceブロックには、メソッド名と入力メッセージ・出力メッセージだけを書きます。ここにHTTPのパスやステータスコードは登場しません。通信の担当はフレームワーク側だからです。

このservice定義を読んでスタブとサーバインターフェースを生成するのがgRPCです。gRPCの仕組みとRESTとの違いは別記事で扱っているので、本記事はデータ定義側に絞ります。

ワイヤ形式の内訳|タグ計算とvarintが決めるバイト数の増減

バイト列の作り方を知っておくと、番号設計と型選択の意味がつながります。

タグ計算の式とワイヤ型6種|フィールド番号1から15が1バイトの理由

各フィールドの先頭には、番号と型を1つに畳んだタグが置かれます。式はfield_numberを3ビット左シフトし、ワイヤ型と論理和したものです。下位3ビットが型を表すため、型は8種類までしか持てません。

実際に定義されているのは6種で、VARINT(0)、I64(1)、LEN(2)、I32(5)、そして廃止されたSGROUP(3)とEGROUP(4)です。3ビット分を型に使うので、番号15までは左シフトしても7ビットに収まり、varint 1バイトで書けます。16になると8ビットを超えて2バイトへ増えます。

varintと固定長の使い分け|sint32とzigzagが効く負値の扱い

整数の既定はvarintです。値が小さいほどバイト数が減り、150は96 01の2バイトになります。ところが負数では逆転します。int32に-1を入れると符号拡張で10バイトまで膨らむためです。

負値が入りうるフィールドにはsint32sint64を使ってください。zigzag符号化で-1が1、1が2、-2が3という並びに写り、小さな絶対値が小さなバイト数に戻ります。逆に、値がほぼ常に大きい整数やハッシュ値ならfixed64のほうが短くなります。

packed既定化と非決定的な直列化|バイト列の比較を避ける根拠

Edition 2023以降、プリミティブ型のrepeatedは既定でpacked扱いになりました。要素ごとにタグを繰り返さず、1つのLENレコードに値を連結する形式です。数千要素の配列では、この既定化だけでサイズが目に見えて縮みます。

もう1点、設計に効く仕様があります。フィールドの出力順は保証されていません。同じメッセージを2回直列化してもバイト列が一致するとは限らないため、バイト列のハッシュを署名や重複排除のキーに使う設計は避けてください。等価判定は生成コードの比較メソッドで行います。

スキーマ進化のルール|後方互換を壊す変更と reserved の運用

Protocol Buffersの本題は書き方ではなく、書いたあとの変更管理です。

安全な変更と危険な変更の境界線|番号の再利用が招くデータ破損事故

ワイヤ形式を壊さない変更は限られています。新しい番号でのフィールド追加、フィールドの削除(番号は封印)、enum値の追加。これらは古い実装が知らない番号を未知フィールドとして読み飛ばすため、両方向で通ります。

危険なのは番号の付け替え、フィールドをoneofへ移動する変更、そして型の変更です。とくに番号の再利用は事故が静かに起きます。旧クライアントが番号5にユーザーIDを載せて送り、新サーバが番号5を金額として読む。例外は出ず、値だけが入れ替わります。

reservedで番号と名前を封じる運用|削除フィールドの後始末

削除時はreservedで番号とフィールド名の両方を封じます。番号を封じれば再利用がコンパイル時に弾かれ、名前を封じればJSON表現で同名の別フィールドが復活する事故を防げます。

運用としては、削除PRのレビュー観点にreservedの追記を必ず含めてください。削除して数か月後に「空いている番号だから」と再割り当てされるのが典型的な失敗パターンです。

buf breakingのFILE・PACKAGE・WIRE・WIRE_JSON四段階

互換性の検査は人のレビューに頼らず機械化が可能です。bufのbuf breakingは、変更前後のスキーマを比較して破壊的変更を検出します。カテゴリは厳しい順にFILE、PACKAGE、WIRE_JSON、WIREの4段階で、厳しいカテゴリを通れば緩いカテゴリも通ります。

選び方の目安を示します。生成コードを配布し、ファイル単位のimportを持つC++やPythonの利用者がいるならFILE(既定)。同一パッケージ内でのファイル移動を許したいならPACKAGE。バイナリ互換だけ守れればよい内部システムならWIREまで緩めても構いません。組織をまたぐスキーマ共有では、この検査をデータ契約として合意する枠組みに組み込むと運用が安定します。

proto3とEdition 2023・2024の違い|protocの対応版と移行判断

Editionsはsyntax宣言の後継です。proto2とproto3という2つの固定的な振る舞いの束を解体し、field_presenceenum_typeutf8_validationといった個別のfeatureとその既定値の集合として再定義しました。ファイル・メッセージ・フィールドの各単位で既定を上書きできます。

使用条件は版に依存します。Edition 2023はprotoc 27.0以降、Edition 2024はprotoc 32.0以降が必要です。proto2やproto3のファイルとの相互importは維持されるため、一斉移行は不要という判断ができます。既存資産が大きいなら、新規ファイルからEditionsで書き、古いファイルは触るタイミングで移す進め方で足ります。

gRPC以外の使い道|ConnectとKafkaでの単体利用と選択肢の整理

gRPCとセットで語られがちですが、切り離して使う構成にも実用性があります。

gRPCが担う通信とProtocol Buffersが担う定義の切り分け

gRPCが担当するのは、HTTP/2上のストリーミング、期限(deadline)、リトライ、認証といった通信の作法です。Protocol Buffersはそこに載るデータの形と、メソッドの入出力宣言だけを担当します。

この分離があるので、gRPCを使わずにProtocol Buffersだけを使えます。REST APIのボディをprotobufバイナリにする、ジョブキューのペイロードに使う、ファイルに保存する。いずれも成立します。

ブラウザからの利用|gRPC-WebとConnectが必要になる制約

ブラウザからgRPCをそのまま呼ぶことはできません。ブラウザのfetch APIがHTTP/2のフレームを直接扱えないためで、間にプロキシを置くgRPC-Webか、HTTP/1.1でも動くConnectのようなプロトコル実装が必要になります。

Connectを選ぶと、同じ.proto定義からブラウザ向けクライアントを生成しつつ、curlでJSONとして叩ける口も同時に持てます。フロントエンドを含む構成では、この二重の入口が開発体験の差になります。

Kafkaのメッセージ形式としての採用|スキーマレジストリとの併用

イベント基盤でProtocol Buffersを使う場合、送受信の当事者が増えるため、スキーマの配布方法が問題になります。.protoをGitで共有するだけでは、古いスキーマで書かれたメッセージが残ったときに読めなくなります。

この領域ではスキーマレジストリで互換性モードを強制する構成が定石です。メッセージ先頭にスキーマIDを埋め、消費側がIDから定義を引く。Protocol Buffers単体では持たない「どの版で書かれたか」の情報を、レジストリ側が補います。

Protocol Buffersを採用する条件と見送る場面の線引き

ここは言い切ります。この形式は万能ではなく、向く相手と向かない相手がはっきり分かれます。

採用が効くのは内部サービス間通信と高頻度・大量データの型付き連携

採用して効果が出るのは、次の条件を複数満たす場合です。通信の両端を自社が管理している。1日あたりの呼び出しが数十万回を超える、あるいは1回のペイロードが大きい。複数言語のサービスが同じデータ構造を共有している。スキーマ変更の頻度が高く、型の食い違いによる障害を経験している。

とくに3つ目と4つ目が揃う現場では、バイト数の削減より「変更を機械が検査できる」ことのほうが価値になります。JSONで運用してきたチームが移行して最初に効くのは、レスポンスの速度ではなく連携ミスの減少です。

見送るべき場面|公開APIと小規模チーム・人が読む必要のある連携

逆に、以下の条件では採用しないでください。外部の不特定多数が使う公開API。この場合、利用者に生成コードの導入を強いる時点で採用率が落ちます。APIの公開管理とゲートウェイの設計を考えるなら、入口はJSONに寄せるほうが現実的です。

サービスが1つか2つ、同一言語、チームが5人以下という規模でも過剰です。スキーマ定義とコード生成の手間に対し、得られるものが釣り合いません。加えて、障害時にログのペイロードを人が読んで切り分ける運用が根付いている現場では、バイナリ化がそのまま調査時間の増加になります。この3条件のいずれかに当てはまるなら、JSONのままでスキーマ検査だけを足すほうが投資対効果は高くなります。

導入コストの実際|生成コードのCI管理と受託開発での持ち込み方

見落とされがちなコストは、生成コードの管理です。リポジトリに生成物をコミットする方式は、環境差による生成結果のブレを避けられる反面、差分レビューが膨らみます。ビルド時生成にすると差分は消えますが、全開発者とCIでプラグイン版を揃える仕組みが要ります。判断の目安は、スキーマを共有する相手の数です。社内数チームまでならビルド時生成、外部へ配布するなら生成物を含むパッケージを配るほうが破綻しません。

初期構築では、.protoを置く独立リポジトリ、生成設定、CIでのbuf breaking検査、この3点を先に固めてから最初のサービスを書き始めてください。順序を逆にすると、後から互換性ルールを入れられなくなります。既存システムへの持ち込みや、複数言語をまたぐ連携基盤の設計で判断に迷う場合は、API開発・システム連携の相談窓口で構成案から検討できます。

よくある質問

Protocol Buffersの導入検討でよく挙がる質問に答えます。

Protocol BuffersとgRPCは何が違いますか?

担当範囲が違います。Protocol Buffersはデータ構造の定義と直列化形式で、gRPCはその定義を使って通信を行うフレームワークです。gRPCはHTTP/2の上で動き、ストリーミングや期限、リトライといった通信の作法を提供する仕組みです。Protocol Buffersは通信路を持たないため、gRPCを使わずKafkaのメッセージやファイル保存に単体で使うこともできます。逆にgRPC側は、原理的には他の直列化形式も選べます。

protoファイルのフィールド番号は途中で変更できますか?

変更してはいけません。番号はワイヤ形式上の唯一の識別子で、フィールド名はバイナリに含まれないためです。番号を付け替えると、旧実装が送った値を新実装が別フィールドとして解釈し、例外を出さずにデータが入れ替わります。フィールドを削除する場合は番号を空けたままにせず、reservedで番号と名前の両方を封じてください。名前の変更は、JSON表現を使っていない限りワイヤ互換を壊しません。

JSONより必ず速くなりますか?

なりません。差が出るのは、レコード数が多い、フィールド名が長い、呼び出し頻度が高いという条件が揃った場合です。ペイロードが数百バイト、通信が数分に1回、経路でgzip圧縮が効いている構成では、体感できる差はほとんど生まれません。加えて、負の整数をint32で持つと符号拡張で10バイトまで膨らむため、型の選び方次第ではサイズが逆転する場面もあります。

proto3とEditionsはどちらで新規開発すべきですか?

2026年8月時点では、条件が揃うならEditionsを選んでください。Edition 2023はprotoc 27.0以降、Edition 2024はprotoc 32.0以降が必要で、使用する言語ランタイムの対応状況も確認が要ります。この条件を満たせない場合はproto3のままで問題ありません。EditionsとProto2・proto3のファイルは相互にimportできるため、既存資産を一斉移行する必要はなく、新規ファイルから順に切り替える進め方が取れます。

生成コードはリポジトリに含めるべきですか?

スキーマを共有する相手の数で決めます。社内の数チームまでなら、ビルド時に生成する方式が扱いやすくなります。差分レビューが生成物で埋まらず、プラグイン版もCIで一元管理できるためです。外部の開発者や他社へ配布する場合は、生成済みコードを含むパッケージを配るほうが導入障壁が下がります。どちらの方式でも、生成条件を設定ファイルで固定し、ローカルとCIで同じ結果になる状態を先に作ってください。

関連記事

資料請求

RELATED POSTS 関連記事