Proto のシリアル化は正規化されていない
多くの人が、シリアライズされたプロトがそのプロトの内容を正準に表現することを望んでいます。ユースケースには、以下が含まれます。
- ハッシュテーブルのキーとしてシリアライズされたプロトを使用する
- シリアライズされたプロトのフィンガープリントまたはチェックサムを取る
- メッセージの等価性をチェックする方法として、シリアライズされたペイロードを比較する
残念ながら、protobuf のシリアライゼーションは正準ではありません(また、正準にはなりえません)。MapReduce など、いくつかの注目すべき例外はありますが、一般的にプロトのシリアライゼーションは不安定であると考えるべきです。このページでは、その理由を説明します。
決定的は正準ではない
決定的なシリアライゼーションは正準ではありません。シリアライザーは、以下のような様々な理由により異なる出力を生成する可能性がありますが、これらに限定されません。
- protobuf スキーマが何らかの形で変更された場合。
- 構築中のアプリケーションが何らかの形で変更された場合。
- バイナリが異なるフラグ(例:opt vs. debug)で構築された場合。
- protobuf ライブラリが更新された場合。
これは、シリアライズされたプロトのハッシュが壊れやすく、時間や空間を超えて安定しないことを意味します。
シリアライズされた出力が変更される理由はたくさんあります。上記のリストは網羅的なものではありません。それらのいくつかは、私たちが望んだとしても正準なシリアライゼーションを保証することを非効率的または不可能にする、問題領域における固有の困難です。その他は、最適化の機会を可能にするために意図的に未定義のままにしているものです。
安定したシリアライゼーションに対する固有の障壁
Protobuf オブジェクトは、前方および後方互換性を提供するために、不明なフィールドを保持します。不明なフィールドの処理は、正準なシリアライゼーションに対する主要な障害です。
ワイヤフォーマットでは、バイトフィールドとネストされたサブメッセージは同じワイヤタイプを使用します。この曖昧さにより、不明なフィールドセットに格納されたメッセージを正しく正準化することが不可能になります。まったく同じ内容がどちらにもなりうるため、メッセージとして扱って再帰するかどうかを知ることは不可能です。
効率のために、実装は通常、不明なフィールドを既知のフィールドの後にシリアライズします。しかし、正準なシリアライゼーションは、フィールド番号に従って不明なフィールドを既知のフィールドとインターリーブすることを必要とします。これは、この機能を必要としないユーザーも含め、すべてのユーザーに大きな効率とコードサイズコストを課すことになります。
意図的に未定義のままになっているもの
正準なシリアライゼーションが実行可能であったとしても(つまり、不明なフィールドの問題を解決できたとしても)、私たちはより多くの最適化の機会を可能にするために、シリアライゼーションの順序を意図的に未定義のままにしています。
- フィールドがバイナリで決して使用されないことを証明できれば、スキーマから完全に削除し、不明なフィールドとして処理できます。これにより、コードサイズと CPU サイクルが大幅に削減されます。
- フィールド番号の順序を破るとしても、同じフィールドのベクトルをまとめてシリアライズすることで最適化の機会があるかもしれません。
このような最適化の余地を残すために、私たちは意図的に一部の構成でフィールド順序をシャッフルし、アプリケーションがフィールド順序に不適切に依存しないようにしたいと考えています。