Proto のシリアル化は正規形式ではありません
シリアル化の仕組みとそれが正規形式ではない理由を説明します。
多くの人が、シリアル化された proto がその内容を正規形式で表現することを望んでいます。ユースケースには以下が含まれます。
- ハッシュテーブルのキーとしてシリアル化された proto を使用する
- シリアル化された proto のフィンガープリントまたはチェックサムを取る
- メッセージの等価性を確認する方法として、シリアル化されたペイロードを比較する
残念ながら、*protobuf のシリアル化は正規形式ではありません(そしてそうはなりえません)*。MapReduce のような注目すべき例外はいくつかありますが、一般的に proto のシリアル化は不安定であると考えるべきです。このページではその理由を説明します。
決定論的でも正規形式ではない
決定論的なシリアル化は正規形式ではありません。シリアライザーは、以下のバリエーションを含む(ただしこれらに限定されない)多くの理由により、異なる出力を生成する可能性があります。
- Protobuf スキーマが何らかの形で変更された場合。
- 構築されているアプリケーションが何らかの形で変更された場合。
- バイナリが異なるフラグ(例:最適化 vs. デバッグ)でビルドされた場合。
- Protobuf ライブラリが更新された場合。
これは、シリアル化された proto のハッシュが脆弱であり、時間的にも空間的にも安定しないことを意味します。
シリアル化された出力が変更される理由はたくさんあります。上記のリストは網羅的ではありません。中には、私たちが望んだとしても正規のシリアル化を保証することが非効率的または不可能になるような、問題空間に固有の困難もあります。その他は、最適化の機会を可能にするために意図的に未定義としているものです。
安定したシリアル化への固有の障壁
Protobuf オブジェクトは、前方互換性および後方互換性を提供するために未知のフィールドを保持します。未知のフィールドは正規形式でシリアル化できません。
- 未知のフィールドは、バイトとサブメッセージを区別できません。どちらも同じワイヤータイプを持つためです。これにより、未知のフィールドセットに保存されたメッセージを正規化することは不可能です。正規化を行う場合、未知のサブメッセージに再帰してフィールド番号でフィールドをソートする必要がありますが、これを行うための十分な情報がありません。
- 効率のために、未知のフィールドは常に既知のフィールドの後にシリアル化されます。しかし、正規のシリアル化では、未知のフィールドと既知のフィールドをフィールド番号でインターリーブする必要があります。これは、この機能を使用しない人を含め、すべての人にとって効率とコードサイズのオーバーヘッドを引き起こします。
意図的に未定義とされたもの
たとえ正規のシリアル化が可能であったとしても(つまり、未知のフィールドの問題を解決できたとしても)、私たちはより多くの最適化の機会を可能にするために、意図的にシリアル化順序を未定義のままにしています。
- もし、あるフィールドがバイナリで決して使用されないことを証明できれば、スキーマから完全に削除し、未知のフィールドとして処理できます。これにより、コードサイズとCPUサイクルを大幅に節約できます。
- 同じフィールドのベクトルをまとめてシリアル化することで最適化する機会があるかもしれません。これはフィールド番号の順序を壊すことになりますが。
このような最適化の余地を残すため、アプリケーションが不適切にフィールド順序に依存しないよう、一部の構成では意図的にフィールド順序をスクランブルしたいと考えています。