ひとりでやるRiak Advent Calendar 2012 day24 - 2i (secondary index)

Seattle Public Library Index

さて最終回となってしまったアドベントカレンダー、みなさんにお楽しみ頂いているだろうか。これまでの記事を見返してみると2iの話がなかったのでここに書き残しておこうと思う。

大前提として、そもそもスケールアウト型のデータベースで外部インデックスは難しいものだ。インデックスとは、実質的に2つのテーブルを保持していて(マスターとインデックス)、データをマスターにinsertするということは、2つのテーブルをトランザクショナルに更新することになる。スケールアウト型のデータベースだとこれが分散トランザクションになる。しかしインデックスなので高速化が目的で、分散トランザクションは遅い&複雑&…ということでそもそもコンピューターサイエンス的に難しい問題である。だから、例えばHBaseやCassandraで高速な外部インデックスを設計できたら驚くほどお金持ちになれるだろう。

それを承知の上でRiakでは外部インデックス(のようなもの)を2iとして実装している。インターフェースとしてはMongoDBのインデックスと同じで、あるレコードに好きな名前でインデックスを張ることができて、カラム名がないといけないとかそういうことはない。しかし、そもそも難しい問題であるから高速でないことは事前にご承知いただきたい。レスポンスもスループットもそんなによくはない。や、一度のクエリで10秒かかりますとかそういうレベルではないのだけども。

内部的にはLevelDBを使っているので、Riakの起動オプションは、全てのマシンで app.config の riak_kv セクションで

    {storage_backend, riak_kv_eleveldb_backend},

としておかないといけない。デフォルトでは riak_kv_bitcask_backend になっているだろう。ちなみに、LevelDBを使っているので、1台で動かしているときは2iを遅いと感じることはそんなにないだろう。

これでRiakを起動したら、まずはデータを入れてみよう。

$ curl -X PUT http://localhost:8098/buckets/spam/keys/ham -d '{ "foobar": "barfoo", num:1 }' \
   -H "X-Riak-Index-foo_bin: barfoo" \
   -H "X-Riak-Index-bar_int: 1
$ curl -X PUT http://localhost:8098/buckets/spam/keys/ham2 -d '{ "foobar": "foofofof", num:2 }' \
   -H "X-Riak-Index-foo_bin: foofofof" \
   -H "X-Riak-Index-bar_int: 2

これで完了。あ、インデックスの参照は遅いけど追加は速い。ふつうのPUT/GET並に速い。

これでデータの参照をする。

$ curl http://localhost:8098/buckets/spam/index/foo_bin/barfoo
{"keys", ["ham"]}

はい、これでキーの一覧がとれた。マッチするものが複数あると複数のリストになる。

$ curl http://localhost:8098/buckets/spam/index/foo_bin/barfoo
{"keys", ["ham"]}

ちなみに辞書順でも数字でも、レンジクエリができるのが個人的には便利だと思っている。

$ curl http://localhost:8098/buckets/spam/index/bar_bin/2/50
{"keys", ["ham2"]}

2iで集めたデータの「範囲」を、MapReduceのinputにすることもできる。各所で僕が「SQLっぽい使い方もできるんですよー'`,、('∀`) '`,、」とか言っているのはこういうことだ*1

curl -XPOST http://localhost:8098/mapred 
  -H 'Content-Type: application/json' 
  -d '{"inputs":{
           "bucket":"mybucket",
           "index":"$bucket",
           "key":"mybucket"
       },
       "query":[{"reduce":{"language":"erlang",
                           "module":"riak_kv_mapreduce",
                           "function":"reduce_count_inputs", 
                           "arg":{"do_prereduce":true}
                          }
               }]
       }'
EOF

ここまで書いて力尽きた。ドキュメントにサンプルがいくつかあるのでこれであとは理解を深めてもらいたい。ちなみに各言語のライブラリにそれっぽいインターフェースがあるのでふいんきで分かると思う。Rubyドキュメントが割と丁寧に書かれているのでオススメだ。

追記 '13/Jan

実際にどういう設計になっているかは、Riak SCRのときの拙作資料をご覧いただきたい。ソースを読んでそんなに速くないことを納得したものだ。

*1:だから、MongoDBのほとんど全てのユースケースをカバーできると主張しているのだ。ただしcapped collectionとかarray?を除く