MongoDB : サルでもわかるMapReduce
最初はこれだけで十分でしょう。
利用事例
ある日、ピカチューがつぶやきサービスでつぶやき始めました。ピカチューのつぶやきに対して、仲間からたくさんの「いいね!」ボタンが押されました。
あなたはつぶやきサービス管理人として、ピカチューのつぶやき数と、ピカチューがゲットした「いいね!」のトータル数を集計してみましょう。
準備
Mongoクライアントを起動して、DBを作成しましょう。./mongodb/bin/mongo
MongoDB shell version: 2.0.1
connecting to: test
> use sample;
switched to db sample
サンプルデータ
ピカチューが3回つぶやいて、「いいね!」がたくさんつきました。> db.entries.save( {username:'pikachu', text:'Pikaaa!' , iine:10 } );
> db.entries.save( {username:'pikachu', text:'PikPika!' , iine:20 } );
> db.entries.save( {username:'pikachu', text:'PikPikchuuu' , iine:30 } );
MongoDB用語でいうと、entriesコレクションにドキュメントを3つ保存したということになります。
(RDBMS用語でいうと、entriesテーブルにレコードを3件insertしたことに相当します。)
集計する
ユーザ名をキーにして、つぶやき件数といいねトータル数を集計してみましょう。期待する結果
ユーザ"pikachu"の集計結果: { count : 3, iine : 60 }
Map関数とReduce関数という2つの関数を作ることによって、これを実現します。
Map関数を作成する
Map関数では、何を集計したいかを定義して、ドキュメントの中から集計に必要な項目を抜き出します。
上記で定義した「期待する結果」をよく見ながら、それに沿う形でemit呼び出しを行います。
var m = function() {
emit(this.username, {count:1, iine: this.iine});
};
ここでは集計そのものは行いません。Map関数の中で、必ずemit関数を呼び出すという決まりになっています。
Map関数内での"this"は、1個のドキュメントを表します。この例でいうとつぶやき1件分です。
MapReduce処理が実行されると、emit関数は下記のような形で3回呼び出されることになります。
emit( 'pikachu', { count:1 , iine:10} );
emit( 'pikachu', { count:1 , iine:20} );
emit( 'pikachu', { count:1 , iine:30} );
Reduce関数を作成する
Reduce関数は集計ロジックを担当します。
var r = function (key, values) {
var result = {count:0, iine:0}; //集計結果を初期化
values.forEach(function(value){
result.count += value.count;
result.iine += value.iine;
});
return result;
}
function(key, values)の部分に注目してください。
keyは、emit関数を呼び出したときの第1引数がきます。つまり'pikachu'という文字列が来ます。
valuesは、emit関数呼び出したときの第2引数がリスト化されたものが来ます。
values = [ {count:1 , iine:10}, {count:1 , iine:10}, {count:1 , iine:10} ];
このkeyとvaluesを使って集計作業を行い、集計結果をオブジェクトにしてreturnしてやるのです。
集計結果オブジェクト(result)の形をよく見ると、Map関数で定義したオブジェクトの形とよく似ています。
これは偶然ではありません。
MapReduceを実行する
実行します。({ out: {inline:1}}というのは、処理結果を保存せずに画面表示させたいときのおまじないです。)
> db.entries.mapReduce(m,r, { out: {inline:1}});
{
"results" : [
{
"_id" : "pikachu",
"value" : {
"count" : 3,
"iine" : 60
}
}
],
"timeMillis" : 1,
"counts" : {
"input" : 3,
"emit" : 3,
"reduce" : 1,
"output" : 1
},
"ok" : 1,
}
お見事!期待する結果が返ってきましたね。
他のユーザを追加
ピカチューの人気をみて、ミュウツーもつぶやき始めました。> db.entries.save( {username:'myutu', text:'myu myu' , iine:1 } );
> db.entries.save( {username:'myutu', text:'myu myu myu' , iine:2 } );
再度mapReduceを実行します。
> db.entries.mapReduce(m,r, { out: {inline:1}});
{
"results" : [
{
"_id" : "myutu",
"value" : {
"count" : 2,
"iine" : 3
}
},
{
"_id" : "pikachu",
"value" : {
"count" : 3,
"iine" : 60
}
}
],
"timeMillis" : 0,
"counts" : {
"input" : 5,
"emit" : 5,
"reduce" : 2,
"output" : 2
},
"ok" : 1,
}
キタ!ユーザごとに、つぶやき数といいね数が集計されました。
これを応用すれば、他にもいろいろな集計を行うことができます。
まとめ
MongoDBのMapReduceを使って、かんたんな集計をする方法を紹介しました。参考
カテゴリ:
MongoDB
JavaScript