ブログ・ア・ラ・クレーム

技術的なメモとかライフログとか。

Schema Registry について書いていく: Confluent Schema Registry の Protocol Buffers & JSON Schema サポート

先日リリースされた Confluent Platform 5.5 より Protocol BuffersJSON Schema のサポートが入ったようです。

www.confluent.io

以前 5.4 を対象に、 Schema Registry を中心に色々記載してみましたが、今回は 5.5 で入ったこの差分を追跡してみます。

syucream.hatenablog.jp

Confluent Control Center 上でスキーマを管理する

定番の Confluent Control Center を使って、ブラウザ上で Schema Registry の設定周りを試してみましょう。 試しに protobuf でスキーマを登録してみた状態が以下のとおりです。 protobuf において .proto ファイルに記述していくスキーマを登録できるようになっています。

f:id:syu_cream:20200502220418p:plain

JSON Schema の場合は例えば以下のようになります。

f:id:syu_cream:20200502220857p:plain

互換性チェックの挙動を試してみる

これだけでは面白くないので protobuf のスキーマを更新していってみます。 まず uint32 age = 3; フィールド追加を行ってみます。フィールド追加は特に互換性を壊しません。というわけでスキーマの更新も無事に行なえます。

syntax = "proto3";
message value_protobuf {
  uint64 id = 1;
  string name = 2;
  uint32 age = 3;
}

今度は field number を変更してみます。早速さきほど追加したフィールドを uint32 age = 42; に書き換えてみました。 この変更もリスクはありそうですが、スキーマ更新は受け入れられます。

syntax = "proto3";
message value_protobuf {
  uint64 id = 1;
  string name = 2;
  uint32 age = 42;
}

さらにフィールドの削除やフィールド名の変更も受け入れられます。

syntax = "proto3";
message value_protobuf {
  uint64 id = 1;
  string fullname = 2;
}

これらの変更は互換性を壊すものではないのでしょうか? Confluent Schema Registry での protobuf の互換性チェックの仕様は以下の通りになっています。

docs.confluent.io

おおむね protobuf の wire format の解釈に影響を及ぼさず、デシリアライズは無事行えるような内容であれば互換性が維持されると見るようです。 フィールド名の変更に対する言及は明記されていませんが、これも wire format の解釈に影響しないからだと思われます。 ・・・ただ実際にはデシリアライズには成功するとしても後続のデータ処理が影響を受けないかどうかは保証されないでしょうし注意が必要です。 フィールドの追加や削除は予期せぬ値のデフォルト値化、フィールド名のリネームはフィールド名を意識した後続処理で問題が発生するかも知れません。

ここから明確に互換性が無い変更も行ってみます。このフィールドの型 uint32string にしてみます。 この場合は予想通り互換性が無いとされてスキーマ更新が拒否されます。

f:id:syu_cream:20200502222110p:plain

// 筆者はこの他にも JSON Schema のスキーマ変更も試してみたのですが、互換性チェックの挙動が読めず諦めてしまいました。

protobuf の扱い周りの実装を読んで見る

5.5 でサポートされたデータフォーマットのバリエーションと各フォーマットの扱いはどうなっているのでしょう。 せっかくなので前節で触れた protobuf 周りの実装を追ってみます。

protobuf 対応した Serializer/Deserializer

ドキュメントから、データフォーマット毎に Serializer, Deserializer の実装が異なるようです。まずはここをエントリポイントとしてみます。

docs.confluent.io

Avro と同様 AbstractKafkaProtobufSerializer, AbstractKafkaProtobufDeserializer に、 protobuf のデータである Message のサブクラスのオブジェクトのシリアライズ・デシリアライズ処理と schema registry とのやり取りの処理が実装されています。 ここで protobuf のスキーマ情報は公式の Descriptor としてではなくラップされた型として扱われます。また protobuf は import 文で他の .proto ファイルを読み込むことができるのですが、この依存関係を解決するロジックも持ち合わせています。

面白い点として Serializer は protobuf の、 protoc で生成された具体的なメッセージクラス (Avro でいう SpecificRecord みたいなもの)を扱い、 Deserializer では DynamicMessage (Avro でいう GenericRecord みたいなもの) を扱うことです。 producer は .proto の記述に従ったメッセージクラスが使えますが、それを consumer が持っている保証はないため妥当な扱いだと思われます。

複数データフォーマットをサポートしたスキーマの表現

5.4 では Schema Registry として考慮していたスキーマが Avro しかありえなかったので、 Schema Registry としては Avro の Schema クラスを扱うような実装になっています。 これが 5.5 では ParsedSchema インタフェースという一段抽象化された構造で扱われます。 protobuf のスキーマを表現する ProtobufSchema、 Avro の AvroSchema, JSON Schema の JsonSchema はそれぞれ ParsedSchema を実装しています。 個々の ParsedSchema 実装の中でスキーマ固有の処理、互換性チェックや protobuf で言う descriptor の扱いなどを担います。

おわりに

簡潔に追っただけですが、妥当な進化を遂げたような Confluent Platform 5.5 でした。 protobuf はコード事前生成を想定した利用シーンが多いですが、 Confluent Platform でのサポートなど利用シーンが増えてくると活用できる機会がさらに増えるかも知れません。 また今回複数データフォーマットのサポートが入った以外にも ksqldb という KSQL の進化系?のような機構が増えて Confluent Platform も進化を続けている印象を受けますね。