38 ポイント 投稿者 GN⁺ 2025-12-05 | 10件のコメント | WhatsAppで共有
  • 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は .proto ファイルを通じて データ構造を明確に定義 します
    • 各フィールドは 厳密な型数値識別子固定された名前 を持ちます
  • 例:
    message User {
      int32 id = 1;
      string name = 2;
      string email = 3;
      bool isActive = 4;
    }
    
  • protoc コマンドにより、Dart、TypeScript、Kotlin、Swift、C#、Go、Rustなどさまざまな言語向けの 自動コード生成 をサポートします
  • 生成されたコードで シリアライズ(writeToBuffer)とデシリアライズ(fromBuffer を行い、手動検証やパースは不要 です
  • その結果、時間の節約保守性の向上 を同時に実現できます

バイナリシリアライズの効率性

  • 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件のコメント

 
GN⁺ 2025-12-05
Hacker Newsの意見
  • JSON はしばしば 曖昧だったり保証されていないデータ を送ることになる。フィールドの欠落、型エラー、キーのタイプミス、文書化されていない構造など、さまざまな問題が起こる。しかし Protobuf は .proto ファイルでメッセージ構造を明確に定義することで、こうしたことは不可能だと主張する記事があった。だが、これは Protobuf の哲学を誤解している。proto3 では required フィールド自体をサポートしていない。公式ドキュメント(Protobuf Best Practices)でも「required フィールドは有害なので削除された」と明記されている。結局、Protobuf クライアントも JSON API と同じように防御的に書く必要がある

    • そのブログには似たような誤解が多い。たとえば SVG の使用に反対する記事では、ベクターフォーマットの自由なスケーリング という利点を考慮していない
    • 問題の核心は言語やクライアント/サーバー実装の違いにすぎない。私は Go の Marshalling 概念 を活用して、クライアントで Gooey フレームワークを使っている。Go の制約を乗り越えれば、かなり型安全に使える。ただし、json:"-" で private フィールドを防ぐことが重要だ。私のプロジェクトは Gooey で見られる
    • この記事は シリアライズ形式と契約(Contract) の概念を混同している
    • ネットワークシステムでは、エンコーディング方式に関係なく データ不一致(Skew) の問題は常に存在する。ただし Protobuf はデコード後に静的型付きオブジェクトを提供する。JSON も検証すればよいが、ほとんどの場合そうしていない。結局 JSON オブジェクトがあちこちで変形され、内部構造を誰も確信できなくなる
    • おそらく元記事の執筆者は、単に Protobuf では 欠落したフィールドがデフォルト値で初期化される という点を言いたかったのだろう。これは「required」フィールドの概念とは別物だ
  • 圧縮された JSON は十分実用的で、初期コミュニケーションコストが低い。もちろんフィールドが欠けたり型が変わったりすれば問題になるが、完全に型付けされた構造を設計し、バージョン同期のためのプロセスを作ろうとする人の大半は失敗する。結局 人間のコストが低いほうが勝つ。だから JSON は、より低い人間的コミュニケーションコストを持つ代替が現れるまでは消えないだろう

    • その通り。大半のアーキテクトは gRPC のような明確な必要性がない限り proto を検討しない。console.log() でそのままデバッグできる代替が現れるまでは、JSON は置き換えられないだろう
    • デバッグもしやすいのが JSON の強みだ。開いて読めばよい。一方 Protobuf には ツール群 が必要になる
    • その通りだ。だが人は設計段階で 15 分余計に投資するより、あとで 3 か月かけて問題を掘り返すほうを選びがちだ
    • JSON が COBOL のように完全に消えることはないだろうが、新規プロジェクト でわざわざ使う理由はない
  • Protobuf は完璧ではない。サーバーとクライアントが異なる時点でデプロイされて 仕様バージョンがずれると 安全性は崩れる。ID の再利用禁止や unknown-field のコピーなどで緩和はできるが、分散システムは本質的に複雑だ。それでも protobuf3protobuf2 の問題をかなり解決した。以前はデフォルト値が設定されたのか欠落しているのか区別できなかったが、今は message 型を使えば解決できる

    • JSON でも Protobuf でも、バージョン互換性テストを CI パイプラインで強制 しなければ安全ではない
    • どんな型システムでも ネットワークを通ると壊れる
  • 記事では「超高効率」と言っていたが、gzip への言及がない。ほとんどのテキストデータはすでに 自動圧縮されて転送 されている。したがって Protobuf は gzip 済み JSON と比較すべきだ

    • 私もいくつものバイナリ形式を試したが、結局 gzipped JSON が圧倒的 に効率的だった
    • JSON の欠点はシリアライズ/デシリアライズ速度だ。それ以外は段階的に解決できる
    • ストリーミング Brotli/zstd JSON/HTML も検討に値する。接続維持中に圧縮ウィンドウを活用できる
    • 関連参考: Auth0 の Protobuf 性能比較記事
    • JSON と mod_deflate の組み合わせは 体感差が非常に大きい
  • より良いプロトコルを擁護するのはよいが、Protobuf が 効率性と使いやすさの両方で JSON を置き換える と見るのは難しい。Protobuf は厳格なスキーマのせいで、JSON が得意な領域を取りこぼしている。むしろ CBOR のほうが JSON の代替として適している。CBOR は JSON のように柔軟でありながら、より簡潔なエンコーディングを持つ

    • ただし Protobuf の 厳格なスキーマ がむしろ長所になることもある。大半の API は JSON スキーマを公開していないからだ。私は ajv や superstruct で検証していたが、Protobuf ならその必要がない
    • ブラウザが CBOR API を直接サポート してくれるとよい。内部実装はすでにあるのだから、難しくはないはずだ
  • 1984 年の ASN.1 は、すでに Protobuf がやっていることをもっと柔軟に実現していた。DER エンコーディングを使えば、そこまで悪くない。ASN.1 DER の例 を見るとよい。Protobuf は達成していることの割に 複雑すぎる

    • ASN.1 は機能が多すぎる。すべての機能をサポートすると 過度に複雑なライブラリ になり、一部だけサポートするとそれはもはや標準 ASN.1 ではない
    • 私は ASN.1 DER を好んでいる。自作の C 実装による DER エンコーダ/デコーダを FOSS として公開した。拡張版「ASN.1X」を作り、JSON のデータモデルを完全に含めた
    • しかし SNMP のようなシステムでは、ASN.1 の 過度な柔軟性 はむしろ問題だった。ベンダーごとに勝手な拡張をしてしまう
    • Google 内部でも Protobuf のシリアライズ/デシリアライズに CPU をかなり消費 していた
    • ASN.1 は 過剰設計(overengineered) されていて、サポートが難しい。継承のような機能は不要だ
  • 私は本番システム全体を Protobuf で構築したことがあるが、運用管理そのものが苦痛 だった。技術的には良さそうに見えても、実際には JSON のほうがずっと単純だ

    • JSON の 可読性とデバッグのしやすさ は過小評価できない。ほとんどのチームは短期的な効率のために JSON を選ぶ
    • どんな問題があったのか気になる。私の経験では、Protobuf の不便さより JSON の データ破損リスク のほうが大きい。Protobuf ならコンパイルエラーで捕まるが、JSON は本番で爆発する
  • Protobuf は素晴らしいが、zero-copy をサポートしていないのが惜しい。Cap’n Proto のような形式ならシリアライズ/デシリアライズのボトルネックをなくせる

    • だが実際には zero-copy のほうが遅いことすらある。キャッシュ内でのコピーはほぼただ同然だが、動的構造を直接扱うとオーバーヘッドが生じる。大半の場合は one-copy で十分だ
    • これは Cap’n Proto のマーケティング由来の主張にすぎず、実際には 性能差はごく小さい。どちらの形式でもネイティブ型 ↔ バイナリ変換は必要だ。ペイロード次第で性能は似たようなものになる
    • これは形式の問題ではなく、ライブラリ実装の問題 かもしれない
  • 私は NodeJS プロジェクトで .proto で API 全体を定義し、Content-Type に応じて proto または JSON で応答 するサーバーを作った。Swagger よりずっと構造的だ。ただし Google がこうした機能を 公式ライブラリとして提供していない のは残念だ。gRPC は HTTP/2 依存のせいで扱いづらい。ちなみに Text proto は最高の静的設定言語 だと思っている

    • こういう目的なら Twirp が向いている。Protobuf も JSON も単純な HTTP 上で扱える
    • ConnectRPC も似たアプローチを提供している。ただし対応範囲はまだ不明瞭だ
  • 私が夢見るバイナリ形式は、スキーマベースでありながらメッセージ内にスキーマを含む ものだ。そうすれば vim プラグインでそのまま読める。数百万個のオブジェクトを扱うとき、1KB のスキーマを 2GB のメッセージに付けても大きな負担ではない

    • Google 内部にはすでにこうした スキーマ内蔵型 Protobuf エコシステム がある。Riegeli が参考になる
    • AvroYardl も似たアプローチを提供している
    • ただし Web サービスでは、むしろ スキーマが 200KB、メッセージが 1KB という場合も多い。そういうときは非効率だ
    • Avro は今でも良い代替だ
 
tested 2025-12-09
 
onixboox 2025-12-08

https://msgpack.org/ これはどうですか?

 
cosine20 2025-12-08

MessagePackも良いです。

 
savvykang 2025-12-06

デバッグ用の公式デコーダーすらないフォーマットを成熟していると主張するのは、矛盾していると思います

 
vipeen 2025-12-06

「デバッグは難しいが」

見送り

 
jjw9512151 2025-12-05

どんなツールにも言えることですが、万能なものはありません。ただ、Protobufも十分に優れたツールだと思います。
特に、さまざまな言語のクライアント向けに、大容量・高頻度(毎秒20回)のデータを組み込み環境で送らなければならないことがあったのですが、そのときは nanopb できれいに実装したことがあります。

 
ifmkl 2025-12-05

そんなに厳密にすると、XMLで来ることになるんじゃないですか(笑)

 
click 2025-12-06

スキーマも dtd で定義しておいて、パーサー側でキャッシュすれば、スキーマは一度だけ送信するのと同じ効果もありそうですね

 
bakyeono 2025-12-05
  • 私が理想とするバイナリ形式は、スキーマベースでありながらメッセージ内にスキーマを含む形です。こうすれば vim プラグインですぐに読めます。数百万個のオブジェクトを扱う場合、1KB のスキーマを 2GB のメッセージに付けても大きな負担にはなりません
  • しかし Web サービスでは、むしろスキーマが 200KB でメッセージが 1KB というケースが多いです。この場合は非効率です

=> どうせスキーマは必ず 1 回は送信しなければならないのではありませんか? JSON であってもスキーマがないのではなく、暗黙的にデータに含まれているので、スキーマを送信していないわけではないと思います。むしろ項目ひとつひとつごとにスキーマを重複して送信しているため、より非効率です。「スキーマベースでありながらメッセージ内にスキーマを含む形」は、かなり良さそうですね。