Kekeの日記

エンジニア、読書なんでも

Apache AvroとProtocol Buffers

本記事

f:id:bobchan1915:20180813130055p:plain

f:id:bobchan1915:20180813130059p:plain

Apache Kafkaなどメッセージ配送のためのOSSを使用していると何かしらシリアライズすることになります。 そこでXMLJSON, ASN.1など選択しないといけません。どれが一体良いのでしょうか?

本記事では、どのようにしてシリアライズすればいいかを解説していこうと思います。

アジェンダ

以下の目次で本記事は構成されています。

Schemaに求められるものとは

1. ロバストネス

ストリーム処理をする中で、もっとも良い点がProducerとConsumerが切り離されていることになります。

しかし、なぜどのデータが正しいか保証できるのでしょう? スキーマがなければ、データが欠損したり、データがコピーできなかったり問題があります。

2. 明確さとセマンティック

アプリケーション間で基準的なものがなく、「データ」というものが不明瞭になります。 必ずといっていいほどバグを招きます。 最近のドキュメントを梱包するべきです。

3. 互換性

スキーマはもっとも問題である「データの変更」に対して対処することができます。 プロダクトが成長するについて、必ずといっていいほどデータの追加、削除または変更があります。 特にデータベースへの保存をするときに、決定的なエラーを招きます。

特にたくさん使われているAPIサーバーやデータベースだと変更に対して、どこへ影響を及ぼすかわかりません。 また、すでに保存している数TBのものを変更しないといけなくなります。

スキーマを設けることでCassandraやHadoopなどに変更なしに格納できるようにしたいです。

4. 会話としてのスキーマ

Producerのアプリを作っている人は、だいたいConsumerであることは限られています。

つまり、スキーマをもってどのようなデータがくるかなど予測する必要があります。

用語

  • スキーマ: データをバイナリ形式にシリアラズ、デシリアライズするための規格。

なぜ必要か

Kafkaなどでストリーミングをしているときに、自分が

  • どのようなデータ形式がくるか
  • どのような型で、デフォルト値はどうなのか
  • どうデータ形式が変化するのか

を全て知り尽くしているケースならば、特に気にすることはありません。 しかし、チームや会社単位で使い回す時に、何かしらの「決まりごと」が必要です。

そのためのものです。

Apache Avro

特徴

  • リッチなデータ構造に対応できる
  • コンパクト、高速なバイナリ形式
  • 永続化ためのコンテナファイル
  • RPC
  • 動的言語との統合性

Apache avroのスキーマはjsonで定義し、以下のようになります。

 { 
     "namespace": "com.test.avro", 
     "type": "record", 
     "name": "Data",  
     "fields": [ 
         {"name": "age", "type": "int"} 
     ] 
 } 

必ずしもjsonを定義する必要がなく、動的に設定することができます。

また

  • Avroはバイナリ化したファイルに格納されるため、のちのアプリで使うことができます。
  • JSONでスキーマは定義されるので、JSONライブラリがある言語では使うことができます。

Apache Kafkaなどと同様に使いたい時は以下のリンクが参考になるかもしれません。

Kafka, Spark and Avro - Part 3, Producing and consuming Avro messages

Protocol Buffers

Google内部で使われていたシリアライズの方法である。

スキーマ定義のための言語があって、以下のような言語であり、.protoに定義するようになる。

syntax = "proto3";
package example.protobuf;

message SimpleMessage {
    message Items {
        string name = 1;
        string value = 2;
    }

    uint64 id = 1;
    Type message_type = 2;
    repeated HeaderItem headers = 3;
}

=1などはタグと呼ばれ、時間に普遍なものを定義する。 これによってフィールドの名前が変更されても、タグで識別できるということである。

2つの比較

  • 動的タイピング: Avroの場合、何かプログラムを書かなくてもすみます。データは全てスキーマと合わせて定義される。
  • 非タグデータ: データが読まれる時にスキーマは有効なため、相当、型の情報が必要ではない。Protocol Bufferのセクションでname = 1などタグを定義する必要がなく、型を定義しないといけなかった。
  • IDを手動で操作する必要がない: スキーマが変更されたときに、データを処理する時にはすでに存在する。Protocol Bufferのセクションでname = 1などタグを定義する必要がない。
  • JSONから直接変換できるか: Avroの場合できる。

以下のようなデータがあったとする。

{
  "time": 1424849130111,
  "customer_id": 1234,
  "product_id": 5678,
  "quantity":3,
  "payment_type": "mastercard"
}

するとスキーマは以下のようになる。

{
  "type": "record",
  "doc":"This event records the sale of a product",
  "name": "ProductSaleEvent",
  "fields" : [
    {"name":"time", "type":"long", "doc":"The time of the purchase"},
    {"name":"customer_id", "type":"long", "doc":"The customer"},
    {"name":"product_id", "type":"long", "doc":"The product"},
    {"name":"quantity", "type":"int"},
    {"name":"payment",
     "type":{"type":"enum",
         "name":"payment_types",
             "symbols":["cash","mastercard","visa"]},
     "doc":"The method of payment"}
  ]
}

これによって - ProducerやConsumerがどのような情報をやりとりするかを知ることができる。 - ドキュメント自体もdocフィールドで梱包している。 - 有効なデータしかトピックに送信されてないため、それ以降のデータ処理を守る。

勉強会などにいってもKafkaトピックごとに設定しないという情報が蔓延しているがSchema Registryというものがあるので、そこにスキーマを登録しておける。 ただし、Conflent社がやっているConfluent Platform上での使えるものです。

参考文献