- Web APIの標準として定着した JSON は読みやすく柔軟ですが、性能と安定性の面では限界があります
- Protobuf(Protocol Buffers) は 厳密な型定義 と 自動コード生成 によってデータ構造を明確に保証します
- バイナリシリアライズ を使うことで、JSONより約3倍以上 データサイズを削減し、転送速度を向上 させます
- サーバーとクライアントが同じ .proto スキーマ を共有するため、型の不一致や手動検証が不要です
- デバッグは難しいものの、性能・保守性・開発効率 の観点ではProtobufの方が現代的なAPIにより適しています
JSONの普遍性と限界
- JSONは 人間が読みやすいテキスト形式 であり、単純な
console.log() だけでもデータを確認できます
- Webとの完全な統合性 により、JavaScriptや各種バックエンドフレームワーク全般で広く採用されています
- フィールドの追加・削除・型変更を自由に行える 柔軟性 を提供しますが、その一方で構造の不一致やエラーが発生する可能性があります
- ツールのエコシステムが豊富 で、テキストエディタや
curl だけでも簡単に扱えます
- しかし、こうした利点があるにもかかわらず、性能と型安全性 の面ではより良い代替手段が存在します
Protobufの概要
- Google が2001年に開発し、2008年に公開した バイナリシリアライズ形式
- 内部システムや マイクロサービス間通信 で広く使われています
- しばしば gRPCと一緒に使わなければならないという誤解 がありますが、Protobufは独立してHTTP APIでも利用できます
- 当初は バイナリ形式の不可視性 によって取っつきにくさがありましたが、効率性と安定性の面で強みがあります
強力な型システムとコード生成
バイナリシリアライズの効率性
- Protobufは テキストではなくバイナリデータ としてシリアライズされるため、非常に コンパクトで高速 です
- 同じデータ(
User オブジェクト)のサイズ比較:
- JSON: 86バイト(空白除去時68バイト)
- Protobuf: 30バイト
- 効率の理由:
- 数値に varintエンコーディング を使用
- テキストキーの代わりに数値タグ を使用
- 空白や不要な構文を削除
- オプショナルフィールドの最適化
- その結果、帯域幅の削減、応答速度の向上、モバイルデータの節約、ユーザー体験の改善 が期待できます
DartベースのProtobuf APIの例
shelf パッケージを使って シンプルなHTTPサーバー を構築し、User オブジェクトをProtobufで返します
- サーバーコードの要点:
User() オブジェクトを生成し、writeToBuffer() でシリアライズ
- レスポンスヘッダーに
'content-type': 'application/protobuf' を指定
- クライアントは
http パッケージと user.pb.dart を使って Protobufデータを直接デコード します
- サーバーとクライアントが同じ
.proto スキーマを共有するため、データ構造の不一致は発生しません
- 同じ方法は Go、Rust、Kotlin、Swift、C#、TypeScript などでもそのまま適用できます
JSONに残る利点
- Protobufは スキーマなしでは意味を解釈しにくい です
- フィールド名の代わりに数値識別子だけが表示されるため、人間には読みづらい です
- 比較例:
- JSON:
{ "id": 42, "name": "Alice" }
- Protobuf:
1: 42, 2: "Alice"
- そのためProtobufでは:
- 専用のデコードツール が必要
- スキーマ管理とバージョン管理 が必須
- それでも、性能と効率性の利点ははるかに大きい といえます
結論
- Protobufは 成熟した高性能 なシリアライズ技術であり、公開APIでも十分に活用可能 です
- gRPCがなくても 一般的なHTTP API で独立して動作します
- 性能、堅牢性、エラー削減、開発効率 をすべて向上させるツールです
- 次世代のプロジェクトで Protobufを導入する価値は十分にあります
10件のコメント
Hacker Newsの意見
JSON はしばしば 曖昧だったり保証されていないデータ を送ることになる。フィールドの欠落、型エラー、キーのタイプミス、文書化されていない構造など、さまざまな問題が起こる。しかし Protobuf は
.protoファイルでメッセージ構造を明確に定義することで、こうしたことは不可能だと主張する記事があった。だが、これは Protobuf の哲学を誤解している。proto3では required フィールド自体をサポートしていない。公式ドキュメント(Protobuf Best Practices)でも「required フィールドは有害なので削除された」と明記されている。結局、Protobuf クライアントも JSON API と同じように防御的に書く必要があるjson:"-"で private フィールドを防ぐことが重要だ。私のプロジェクトは Gooey で見られる圧縮された JSON は十分実用的で、初期コミュニケーションコストが低い。もちろんフィールドが欠けたり型が変わったりすれば問題になるが、完全に型付けされた構造を設計し、バージョン同期のためのプロセスを作ろうとする人の大半は失敗する。結局 人間のコストが低いほうが勝つ。だから JSON は、より低い人間的コミュニケーションコストを持つ代替が現れるまでは消えないだろう
console.log()でそのままデバッグできる代替が現れるまでは、JSON は置き換えられないだろうProtobuf は完璧ではない。サーバーとクライアントが異なる時点でデプロイされて 仕様バージョンがずれると 安全性は崩れる。ID の再利用禁止や unknown-field のコピーなどで緩和はできるが、分散システムは本質的に複雑だ。それでも
protobuf3はprotobuf2の問題をかなり解決した。以前はデフォルト値が設定されたのか欠落しているのか区別できなかったが、今はmessage型を使えば解決できる記事では「超高効率」と言っていたが、gzip への言及がない。ほとんどのテキストデータはすでに 自動圧縮されて転送 されている。したがって Protobuf は gzip 済み JSON と比較すべきだ
より良いプロトコルを擁護するのはよいが、Protobuf が 効率性と使いやすさの両方で JSON を置き換える と見るのは難しい。Protobuf は厳格なスキーマのせいで、JSON が得意な領域を取りこぼしている。むしろ CBOR のほうが JSON の代替として適している。CBOR は JSON のように柔軟でありながら、より簡潔なエンコーディングを持つ
1984 年の ASN.1 は、すでに Protobuf がやっていることをもっと柔軟に実現していた。DER エンコーディングを使えば、そこまで悪くない。ASN.1 DER の例 を見るとよい。Protobuf は達成していることの割に 複雑すぎる
私は本番システム全体を Protobuf で構築したことがあるが、運用管理そのものが苦痛 だった。技術的には良さそうに見えても、実際には JSON のほうがずっと単純だ
Protobuf は素晴らしいが、zero-copy をサポートしていないのが惜しい。Cap’n Proto のような形式ならシリアライズ/デシリアライズのボトルネックをなくせる
私は NodeJS プロジェクトで
.protoで API 全体を定義し、Content-Type に応じて proto または JSON で応答 するサーバーを作った。Swagger よりずっと構造的だ。ただし Google がこうした機能を 公式ライブラリとして提供していない のは残念だ。gRPC は HTTP/2 依存のせいで扱いづらい。ちなみに Text proto は最高の静的設定言語 だと思っている私が夢見るバイナリ形式は、スキーマベースでありながらメッセージ内にスキーマを含む ものだ。そうすれば vim プラグインでそのまま読める。数百万個のオブジェクトを扱うとき、1KB のスキーマを 2GB のメッセージに付けても大きな負担ではない
TypeSpec
https://typespec.io/
https://msgpack.org/ これはどうですか?
MessagePackも良いです。
デバッグ用の公式デコーダーすらないフォーマットを成熟していると主張するのは、矛盾していると思います
「デバッグは難しいが」
見送り
どんなツールにも言えることですが、万能なものはありません。ただ、Protobufも十分に優れたツールだと思います。
特に、さまざまな言語のクライアント向けに、大容量・高頻度(毎秒20回)のデータを組み込み環境で送らなければならないことがあったのですが、そのときは
nanopbできれいに実装したことがあります。そんなに厳密にすると、XMLで来ることになるんじゃないですか(笑)
スキーマも dtd で定義しておいて、パーサー側でキャッシュすれば、スキーマは一度だけ送信するのと同じ効果もありそうですね
=> どうせスキーマは必ず 1 回は送信しなければならないのではありませんか? JSON であってもスキーマがないのではなく、暗黙的にデータに含まれているので、スキーマを送信していないわけではないと思います。むしろ項目ひとつひとつごとにスキーマを重複して送信しているため、より非効率です。「スキーマベースでありながらメッセージ内にスキーマを含む形」は、かなり良さそうですね。