|
Based on 68 MongoDBでのmap/reduceは、バッチでのデータの更新や集計処理で便利に使えます。すべての入力はコレクションから、すべての出力はコレクションへ、という方針はHadoopに少しにています。SQLでGROUP BYを使いたいようなとき、MongoDBではmap/reduceを使うのが正しいです。
概要
map/reduce はデータベース [command] を経由して、呼び出されます。 データバースは一時的なコレクションを出力結果用に作成します。この一時的なコレクションは、クライアントのコネクションが閉じたとき、または明示的にdropされた場合削除されます。また、永続的な出力用のコレクション名を指定することもできます。 map と reduce ファンクションはJavaScriptで書き、サーバ上で実行されます。 コマンドの文法: db.runCommand(
{ mapreduce : <collection>,
map : <map ファンクション名>,
reduce : <reduce ファンクション名>
[, query : <クエリーフィルタオブジェクト>]
[, sort : <クエリをソート。最適化用です>]
[, limit : <最大何件のオブジェクトを返すか>]
[, out : <出力先のコレクション名>]
[, keeptemp: <true|false>]
[, finalize : <finalize ファンクション>]
[, scope : <object where fields go into javascript global scope >]
[, verbose : true]
}
);
結果: { result : <collection_name>,
counts : {
input : <走査されたオブジェクトの数>,
emit : <emitが呼ばれた回数>,
output : <出力結果の数>
} ,
timeMillis : <job_time>,
ok : <1_if_ok>,
[, err : <errmsg_if_error>]
}
MongoDB shell にはこのためのコマンドヘルパーがあります。 db.collection.mapreduce(mapfunction,reducefunction[,options]); map, reduce, finalize ファンクションはJavascriptで書きます。 map ファンクションmap ファンクションは、 評価中の現在のオブジェクトを{{this}} 変数で指します。 mapファンクションは、 最低一回は{{emit(key,value)}}を呼ぶ必要があります。また何回でも呼ぶこともできます。 function map(void) -> void reduce ファンクションreduce ファンクションは、キーと配列(array)を受け取ります。受け取った値を減らし(reduce)、結果を返します。 function reduce(key, value_array) -> value mapreduceエンジンは、reduceファンクションを繰り返し呼ぶことがあります。そのため、reduceファンクションはidempotentになる必要があります。 これは以下の条件を満たす必要があること、ということです。 for all k,vals : reduce( k, [reduce(k,vals)] ) == reduce(k,vals)
もし、一回だけ実行する必要がある場合には、finalize ファンクションを作ってください。 注意: 現在のところ、reduceファンクションから配列(array)を返すことはできません。(オブジェクトか数値を返すのが一般的です) finalize ファンクションfinalize ファンクションは、reduceの後に走ります。 このファンクションはオプションで、多くのmap/reduce処理では必要ではありません。 finalize ファンクションはキーと値を受け取り、finalizeされた結果を返します。 function finalize(key, value) -> final_value shard環境shardな環境では、map/reduceでの操作はすべてのshardで並行に処理されます。 例シェルでの例 1次の例では、 events コレクションが次の構造のオブジェクトを持つことを仮定しています。 { time : <time>, user_id : <userid>, type : <type>, ... }
mapreduceを使い、"sale"というタイプのイベントを1つ以上持つユーザを抽出します。 > m = function() { emit(this.user_id, 1); }
> r = function(k,vals) { return 1; }
> res = db.events.mapreduce(m, r, { query : {type:'sale'} });
> db[res.result].find().limit(2)
{ "_id" : 8321073716060 , "value" : 1 }
{ "_id" : 7921232311289 , "value" : 1 }
もし、ユーザが何回のeventを経験したかという情報も出力したい場合には、reduceファンクションを以下のように書き換えます。 > r = function(k,vals) {
... var sum=0;
... for(var i in vals) sum += vals[i];
... return sum;
... }
注意。ここで、単純に vals.length を返すことはできません。reduceファンクションは複数回呼ばれることがあるからです。 シェルでの例 2$ ./mongo
> db.things.insert( { _id : 1, tags : ['dog', 'cat'] } );
> db.things.insert( { _id : 2, tags : ['cat'] } );
> db.things.insert( { _id : 3, tags : ['mouse', 'cat', 'dog'] } );
> db.things.insert( { _id : 4, tags : [] } );
> // map function
> m = function(){
... this.tags.forEach(
... function(z){
... emit( z , { count : 1 } );
... }
... );
...};
> // reduce function
> r = function( key , values ){
... var total = 0;
... for ( var i=0; i<values.length; i++ )
... total += values[i].count;
... return { count : total };
...};
> res = db.things.mapreduce(m,r);
> res
{"timeMillis.emit" : 9 , "result" : "mr.things.1254430454.3" ,
"numObjects" : 4 , "timeMillis" : 9 , "errmsg" : "" , "ok" : 0}
> db[res.result].find()
{"_id" : "cat" , "value" : {"count" : 3}}
{"_id" : "dog" , "value" : {"count" : 2}}
{"_id" : "mouse" , "value" : {"count" : 1}}
> db[res.result].drop()
その他の例
永続的なコレクションについての注意たとえ永続的なコレクション名が指定されたときでも、一時的なコレクション名が処理中に使われます。map/reduce が終わったとき、一時的なコレクションが永続的なコレクション名にリネームされます。このため、一時的な不完全なデータについて心配することなく、同じコレクション名を使い定期的にmap/reduce処理を実行することができます。これは、定期的に統計情報をコレクションに出力するときにとても便利です。 |

IF YOU HAVE A QUESTION, POST IT TO THE USER GROUP.
These pages are fine for comments, but for questions, your best bet will always be the MongoDB User Group. blog comments powered by Disqus