Protoシリアライゼーションは正規ではない

シリアライゼーションの仕組みと、それが正規ではない理由について説明します。

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

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

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

決定論的シリアライゼーションは正規ではない

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

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

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

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

安定したシリアライゼーションへの本質的な障壁

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

  1. 不明なフィールドは、バイトとサブメッセージを区別できません。どちらも同じワイヤタイプを持っているためです。これにより、不明なフィールドセットに格納されているメッセージを正規化することが不可能になります。正規化しようとする場合、不明なサブメッセージに再帰してフィールドをフィールド番号でソートする必要がありますが、これを行うための十分な情報がありません。
  2. 不明なフィールドは、効率のために常に既知のフィールドの後にシリアライズされます。しかし、正規シリアライゼーションでは、不明なフィールドを既知のフィールドとフィールド番号でインターリーブする必要があります。これは、機能を使用しない人を含め、すべての人に効率とコードサイズのオーバーヘッドを引き起こします。

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

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

  1. フィールドがバイナリで一度も使用されていないことを証明できれば、スキーマから完全に削除し、不明なフィールドとして処理できます。これにより、コードサイズとCPUサイクルを大幅に節約できます。
  2. たとえフィールド番号の順序が崩れても、同じフィールドのベクターをまとめてシリアライズすることで最適化できる可能性があります。

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