Proto のシリアル化は正規化されていない

シリアライゼーションがどのように機能し、なぜそれが正規化されないのかを説明します。

多くの人が、シリアライズされたprotoがそのprotoの内容を正規に表現することを望んでいます。ユースケースには以下のようなものがあります。

  • ハッシュテーブルのキーとしてシリアライズされたprotoを使用する
  • シリアライズされたprotoのフィンガープリントやチェックサムを取得する
  • メッセージの等価性を確認する方法として、シリアライズされたペイロードを比較する

残念ながら、protobufのシリアライゼーションは正規化されておらず、(また、正規化することもできません)。MapReduceなどのいくつかの顕著な例外はありますが、一般的にprotoのシリアライゼーションは不安定であると考えるべきです。このページではその理由を説明します。

決定的は正規化ではない

決定的なシリアライゼーションは正規化ではありません。シリアライザは、以下のバリエーションを含むがこれらに限定されない多くの理由で、異なる出力を生成する可能性があります。

  1. protobufスキーマが何らかの形で変更された場合。
  2. 構築中のアプリケーションが何らかの形で変更された場合。
  3. バイナリが異なるフラグ(例:opt vs. debug)でビルドされた場合。
  4. protobufライブラリが更新された場合。

これは、シリアライズされたprotoのハッシュは脆弱であり、時間や空間を超えて安定していないことを意味します。

シリアライズされた出力が変更される理由はたくさんあります。上記のリストは網羅的なものではありません。その中には、もし私たちが正規化シリアライゼーションを保証しようとしても、非効率的または不可能にするであろう問題空間の固有の困難さが含まれます。その他は、最適化の機会を許容するために意図的に未定義のままにしているものです。

安定したシリアライゼーションに対する固有の障壁

Protobufオブジェクトは、前方互換性と後方互換性を提供するために未知のフィールドを保持します。未知のフィールドは正規にシリアライズすることはできません。

  1. 未知のフィールドは、バイトとサブメッセージを区別できません。両者は同じワイヤタイプを持つためです。これにより、未知のフィールドセットに保存されているメッセージを正規化することが不可能になります。もし正規化するのであれば、フィールド番号でフィールドをソートするために未知のサブメッセージに再帰的に入る必要がありますが、そのためには十分な情報がありません。
  2. 未知のフィールドは、効率のために常に既知のフィールドの後にシリアライズされます。しかし、正規化シリアライゼーションでは、フィールド番号によって未知のフィールドと既知のフィールドを交互に配置する必要があります。これは、この機能を使用しない人にとっても、効率とコードサイズのオーバーヘッドを引き起こすことになります。

意図的に未定義のままにされていること

たとえ正規化シリアライゼーションが実現可能であったとしても(つまり、未知のフィールドの問題を解決できたとしても)、私たちはより多くの最適化の機会を許容するために、意図的にシリアライゼーションの順序を未定義のままにしています。

  1. あるフィールドがバイナリで決して使用されないと証明できれば、そのフィールドをスキーマから完全に削除し、未知のフィールドとして処理することができます。これにより、コードサイズとCPUサイクルを大幅に節約できます。
  2. 同じフィールドのベクターをまとめてシリアライズすることで最適化する機会があるかもしれません。これはフィールド番号の順序を破ることになりますが。

このような最適化の余地を残すために、アプリケーションが不適切にフィールドの順序に依存しないように、一部の構成では意図的にフィールドの順序をスクランブルしたいと考えています。