Treasure DataのPlazmaDBを理解する

こんにちは。Treasure Dataの斉藤です。出張中に時間ができたのでシアトル空港でこの記事を書いています。日本語でブログを書くのはものすごく久しぶりなのですが、Treasure Dataの列志向(columnar)圧縮ストレージであるPlazmaDBについて紹介していきたいと思います。

Treasure Dataでは2014年現在まで5兆(trillion)件を超えるレコードが取り込まれており、一秒あたりでは40万以上(!)のレコードを処理しています。

![td-record-counts_pptx.png](https://qiita-image-store.s3.amazonaws.com/0/22352/10dfd026-356a-385d-d540-160db06866fb.png)

2013年のTwitterでは１秒あたり5,700 tweets処理していたとのことなので、その処理量の大きさが実感できるのではないでしょうか。この量のレコードをそのまま蓄積するのではストレージ量が膨大になってしまいますので、Treasure Dataではレコードを列分解し、MessagePack形式に変換＋圧縮処理を施すことでデータサイズを約10分の1に抑えています。

## PlazmaDB

PlazmaDBの特徴を挙げると、
・レコードを列分解(MessagePack形式のデータ列に変換)して圧縮

・スキーマは自動的に付与されるため (**implicit schema**)、
![Pasted_Image_12_14_14__11_49.png](https://qiita-image-store.s3.amazonaws.com/0/22352/ed004c17-3366-ae41-7b84-076acfad4671.png)
列の追加が容易です。
![Pasted_Image_12_14_14__11_50-2.png](https://qiita-image-store.s3.amazonaws.com/0/22352/3c1f7736-29a9-49fe-5375-62fa0f57477c.png)
スキーマの再設計に伴うDBの再構築などのコストが全くありません。

・１時間ごとのまとまりでパーティションが作成されるため、直近のデータの検索が高速になります。
![Pasted_Image_12_14_14__11_49-2.png](https://qiita-image-store.s3.amazonaws.com/0/22352/0168c61f-273b-070c-6000-8b764795c3ee.png)

・また、列内に複数の型のデータを混在させることができ、クエリ実行時に要求される型に合わせて変換されます。

PlazmaDBでは[MessagePack](http://msgpack.org/)形式のデータを内部的に使っているのが肝で、[fluentd](http://fluentd.org/)との親和性もさることながら、各要素が型情報(int, string, arrayなどなど）を自ら持っているデータ(self-describing format)なので、例えば、最初はint型でidを表現していたが後からstring型のA001, A002などの文字列に変更したくなった場合でも柔軟に対応できます。

明示的にスキーマを指定することで(**explicit schema**)、string型で保存されているデータをint型のデータとして見せることもできます。型が指示通りに変換できない場合にはNULLとして出力されますが、実際のデータが消えているわけではありません。
![Pasted_Image_12_14_14__12_00.png](https://qiita-image-store.s3.amazonaws.com/0/22352/4cf0db67-dda0-462e-91af-85d84fbdc3cb.png)

implicit/explicit schemaを活用した業務フローについてはこちらの記事も参考にしてください: [データ解析を簡単に 〜 スキーマ管理の権限移譲](http://qiita.com/kzk9/items/eb597306dc9b58141459)

PlazmaDBではArray型などのデータも列中に保存できるため、Treasure Dataの[Prestoによる検索サービスでも利用できます](http://prestodb.io/docs/current/functions/array.html)。このようにテーブルの要素にネストしたデータを保存する形式は、第１正規形(1st Normal Form:1NF。要素は必ず１つのデータを持つ)ではなくなるため、Non-First Normal Form (NF^2)という呼び方をすることもあります。

JSONやXMLでもこのようなNF^2のデータを表現できるのですが、JSONは要素を１つ読み終えるまで型の判定ができないため読み込みの性能が悪くなりますし、XMLデータでは要素以外に空白などの情報も含まれるためMessagePack形式ほど高速にデータを処理することはできません。PlazmaDB内部では、旧v06より性能を２倍改善した[msgpack-java v07](https://github.com/msgpack/msgpack-java)を利用しており、非常に高速に列データの読み出しが可能となっています。XMLの研究が普及してきた1997年頃にMessagePackがあれば、XMLの仕様に由来するあらゆる問題（parsing, text、空白の扱い、XMLスキーマなどの複雑な外部スキーマ定義など）に苦しむ必要はなかったのではないかと思います。

## GiSTによる時間軸検索の高速化
PlazmaDBで１時間単位で作成されたpartitionを高速に見つけるために、PostgreSQLの[GiST index](http://gist.cs.berkeley.edu/)が利用されています。(data set id, (partition start_time, end_time)) の複合keyでindexが作成されており、ユーザーデータのID、時系列の範囲の組で素早く検索することができます。もしGiSTを用いずに、data set id, start/end timeの各々にindexを作成してしまうと、PostgreSQLのオプティマイザが各indexを独立に検索してしまうためディスクI/O量が多くなってしまいます。

このGiST indexは、TD_TIME_RANGE(time, start_time, last_time)という、現在TDで唯一indexを使うUDFの裏側で利用されています。例えば2014-12-14以降のデータを取得するにはwhere文中にTD_TIME_RANGE(time, ‘2014-12-14 JST’) のように条件を指定するとレコードのスキャン量を減らし、高速に検索結果が得られるようになります。

[GiST index](http://gist.cs.berkeley.edu/)はVLDB95でHellersteinらが発表した研究でPostgreSQLには古くから取り込まれている機能です。Hellersterinは、データベース研究の歴史を整理した[Readings in Database Systems（通称Redbook）](http://redbook.cs.berkeley.edu/)の著者でもあり、最近では[RAMPトランザクション P. Bailisとの共著. SIGMOD2014](http://www.bailis.org/papers/ramp-sigmod2014.pdf)など、レプリカがあるデータベースで一貫性の制御が必要ない更新をうまく抜き出すセマンティクスを導入することでTPC-Cのトランザクションを大幅に高速化するなど、トランザクション研究でも活躍している先生です。参考までに。

## データベース研究と実世界のDB

PlazmaDBは、JSON, XMLデータが持つ良い性質を抽出し、GiSTインデックスなど過去の研究の成果も上手に使ったデータベースになっています。僕も長らくXMLDBを研究してきましたが、この理想形とも言えるDBがTreasure Dataで実装され、多くの人に便利に使っていただいているというのは感慨深いものがあります。PlazmaDBの機能を抽出したオープンソースの簡易版も用意できると良いですが、作ってみたいという方は[ぜひこちらまでご応募ください](http://www.treasuredata.com/jp/careers.php)。