ページ

2010年11月22日

mongodbのmap reduceを使ってみた

MongoDBにはMap Reduceを簡単に使う機能があります。それ以外にコレクション(テーブル)にgroupというメソッドが定義されていて、RDBMSのgroup by相当のことができるとマニュアルには書かれています。ただ、次のような怖い注意書きがあります。

注意: 現在のところ、shardの環境では、group()の代わりにmap/reduceを必ず使ってください。
結果はなるべく小さくしてください(10,000キー以内)。大きすぎる場合例外が発生します。
Shard環境では問答無用にMap Reduceを使うしかなさそうです。結果はなるべく小さくしろと言うことですが、10000キーあればそこそこ大きいような気がします。最初の制限の「現在のところ」というのが気にならなくもないですが、処理結果はMapReduceと大差ないし、MapReduceの方がもう少しいろいろできそうな気もします。たとえば、結果は実行後すぐに取得できますが、これをコレクションとしてMongoDBの中に永続化するとかです。なので、groupメソッドはいらねんじゃね?Map Reduceだけで十分じゃね?と思います。

さて、それで、MapReduceですが、map関数とreduce関数をJavaScriptで書くだけです。PythonでもJavaScriptです。はい。それ以外にもfinalizeするときのメソッドも定義できます。マニュアルをよむと、結果で平均値を求めたりするらしいです。reduceの中でやっちゃえばいいような気もするので、あまり使い道を思いつきません。

それじゃ、コードのお時間です。
import pymongo
import pymongo
from pymongo.code import Code
conn = pymongo.Connection()

tbl = conn.my_db.my_table

m = Code("""
function() {
  emit(this.url, this);
}
""")

r = Code("""
function(key, values) {
  var total = 0;
  var len = values.length;
  for (var i=0; i<len; i++) {
    total += values[i].count;
  }
  var url = values[0].url;

  return { count:total, url: url};
}
""")
res = tbl.map_reduce(m, r
for r in res.find():
    print r["value"]["count"]

my_tableは、urlとcountというフィールドを持ちます。それぞれ文字列と数字です。map関数はmです。これは、URLをキーとしてemitしてます。valueはめんどいのでレコード自身です。rがreduce関数になります。これは、valuesはemitしたvalueが配列になって渡されます。一個づつイテレートしながらcountの光景を求めています。終わったら、countとurlを返しています。
map_reduceで実行すると結果として、コレクションが返されます。このコレクションに対して、findなどいろいろできます。結果を永続化するにはout="result"という引数をmap_reduceに追加してあげればよいです。簡単です。
新しく作られたコレクションは、keyとvalueという二つのフィールドからなります。キーはreduce関数に渡されたものです。valueはreduce関数が返したものです。
group関数を使った場合は、結果が配列ですべてオンメモリで返されます。ソートを行うにしても自前でソートしないと行けません。いやんですね。

まあ、groupは使わずにmap reduceでよくね?という話です。
でわでわ。

2 件のコメント:

匿名 さんのコメント...

速度が全然違いますが

liris さんのコメント...

でも、シャード環境で使えない、件数が多いとメモリに全部展開されるのでスケーラブルでない、など制限がありすぎて、多くの場合、速度のメリットよりデメリットが上回ると思っています。