入門 Avro 第2版
オライリーっぽいタイトルつけてみました。こんにちは。
最近はダラダラと過ごしてしまっていますが、今回はApache Avroをちょっと触ってみたのでその記事を書こうと思います。
Avroとは?
効率的にデータが保存できる、バイナリのフォーマットです。
いわゆる行指向フォーマットというやつで、OLTP処理の場合に向いているフォーマットとのこと。
そのあたりの全体像はこのブログが詳しかったので、ぜひ。
カラムナフォーマットのきほん 〜データウェアハウスを支える技術〜 - Retty Tech Blog
また、こちらの本
の中には、Avroにおいて鍵となる考え方は、ライターのスキーマとリーダーのスキーマは同一である必要がなく、互換性さえあればよいという考え方です。
とあります。
つまり、スキーマ定義(このデータは"Name”(String)、"Age"(int)という列を持っているぞ!のようなもの)がライターとリーダーで別々で持てるということです。
(互換性は持っておく必要あり)
別々で持てると、例えば「次のアプリからは今まであった"Age"ってカラムいらなくなったんだよなぁ~」というときは、リーダーのスキーマ定義だけ変更するとかの対応ができますよね。
スキーマの進化に対して、修正のハードルが低いんですよ、という内容が載っていました。
と、ここまで読んだら、実際にコード書いて確かめたくなりますよね。
というわけで、実際にpythonで書いてみました。
まずは公式チュートリアル
Apache Avro 1.11.0 Getting Started (Python)
とりあえずここに書いてある内容を読んで動かせば、avroファイルの書き込み、読み取りのイメージはつかめます。
ただ、
- コードと説明が背景同じで書かれているのでどの部分がコードなのか分かりにくい
- python3の実装と書いてあるのに、print文の書き方が2系(print("aaa")ではなく、print "aaa"になってる)
という罠があるので気を付けてください。
一応、ここからはチュートリアルの内容を見ていきます。
以下、チュートリアルのコードを私が見やすいように整形したものです。
・pythonコード
import avro.schema from avro.datafile import DataFileReader, DataFileWriter from avro.io import DatumReader, DatumWriter def avro_write(write_file, schema): writer = DataFileWriter(open(write_file, "wb"), DatumWriter(), schema) writer.append({"name": "Alyssa", "favorite_number": 256}) writer.append({"name": "Ben", "favorite_number": 7, "favorite_color": "red"}) writer.close() def avro_read(read_file): reader = DataFileReader(open(read_file, "rb"), DatumReader()) for record in reader: print(record) reader.close() write_file = "users.avro" read_file = "users.avro" schema = avro.schema.parse(open("user.avsc", "rb").read()) avro_write(write_file, schema) avro_read(read_file)
{"namespace": "example.avro", "type": "record", "name": "User", "fields": [ {"name": "name", "type": "string"}, {"name": "favorite_number", "type": ["int", "null"]}, {"name": "favorite_color", "type": ["string", "null"]} ] }
単純に、書き込みと読み込みを行っています。
このpythonファイルを実行すると、実行結果はこの2行です。
{'name': 'Alyssa', 'favorite_number': 256, 'favorite_color': None}
{'name': 'Ben', 'favorite_number': 7, 'favorite_color': 'red'}
Alyssaの"favorite_color"は、python内の書き込みで指定していないため、結果出力ではNoneになっていますね。
これは、スキーマ定義で"favorite_color"のtypeに"null"が含まれているためです。
試しにスキーマ定義の"null"を削除してみると、エラーが返ってきました。
さて、基本的な読み込み書き込みはこれで私も理解したのですが、ここで1つ疑問が。
書籍内では、リーダーとライター別々のスキーマ持てるって聞いたけど、このチュートリアルだとライターしかスキーマ定義使ってなくね?
ということです。
確かに、読み込みの時はスキーマを使わなくても読み込めています。
どうやら、読み込みの時何も指定しなくても、ライターの定義を内部で保持していて、それを勝手に使ってくれるようです。
ただ、どうせなら別々のスキーマ定義使いたい!ということでその実装方法も調べてみました。
ライターとリーダーで別々のスキーマ定義を使ってみる
まずは、使うスキーマ定義を。
v1として前使っていたものと同じスキーマ、v2としてその中の"favorite_color"を落としたものを使います。
そして、ライターではv1、リーダーではv2を使ってみます。
つまり、ライターの時はfavorite_colorが定義されているからデータとしても書き込まれているけど、
リーダーの時はfavorite_colorを無視したい!というユースケースですね。
・スキーマ定義_v1(user_v1.avsc)
{"namespace": "example.avro", "type": "record", "name": "User", "fields": [ {"name": "name", "type": "string"}, {"name": "favorite_number", "type": ["int", "null"]}, {"name": "favorite_color", "type": ["string", "null"]} ] }
・スキーマ定義_v2(user_v2.avsc)
{"namespace": "example.avro", "type": "record", "name": "User", "fields": [ {"name": "name", "type": "string"}, {"name": "favorite_number", "type": ["int", "null"]} ] }
では、使うpythonコードです。
変更点は、リーダーにもスキーマを使うようにしたところです。
そのやり方がどこにも書いてなかったので結構てこずりましたが、頼るべきものは公式。
avro/io.py at release-1.7.7 · apache/avro · GitHub
上記の実際のavroのpython実装を見ると、DatumReader()のinitにこんなものが書いてあるじゃないですか。
確かに、さっきまでの公式チュートリアルだとDatumReader() と引数なしで使っていた。
なので、リーダーのスキーマだけ指定するように
DatumReader(None, reader_schema)
という感じに使ったら、見事動きました。
以下、完成品です。
import avro.schema from avro.datafile import DataFileReader, DataFileWriter from avro.io import DatumReader, DatumWriter def avro_write(write_file, schema): writer = DataFileWriter(open(write_file, "wb"), DatumWriter(), schema) writer.append({"name": "Alyssa", "favorite_number": 256}) writer.append({"name": "Ben", "favorite_number": 7, "favorite_color": "red"}) writer.close() def avro_read(read_file, schema): # ここでリーダーのスキーマ定義を使う reader = DataFileReader(open(read_file, "rb"), DatumReader(None,schema)) for record in reader: print(record) reader.close() write_file = "users.avro" read_file = "users.avro" schema_v1 = avro.schema.parse(open("user_v1.avsc", "rb").read()) schema_v2 = avro.schema.parse(open("user_v2.avsc", "rb").read()) avro_write(write_file, schema_v1) avro_read(read_file, schema_v2)
実行結果はこちら。
{'name': 'Alyssa', 'favorite_number': 256}
{'name': 'Ben', 'favorite_number': 7}
見事、リーダーのスキーマ定義だけ落とした"favorite_color"が出力されていませんね。
これで、明日から急にAvroで別々のスキーマ定義したいといわれても対応できます。