[MongoDB]サルでもわかるMapReduce 3 PVとUUを同時に集計する
//まず集計で使うためのユーティリティ関数を自作する
//ここで定義した関数はMapReduce時にscopeオプションで渡してやります。
// 日付オブジェクトをYYYY-MM-DD形式に変換するユーティリティ関数
var getYMD = function (d) {
yyyy = d.getFullYear();
mm = d.getMonth() + 1;
dd = d.getDate();
mm = (new Number(mm)).zeroPad(2);
dd = (new Number(dd)).zeroPad(2);
return yyyy + "-" + mm + "-" + dd;
};
// 配列のユニーク要素数を数えるユーティリティ関数
var uniqCount = function (ary) {
var tmpHash = {};
var count = 0;
for (var i = 0; i < ary.length; i++) {
tmpHash[ary[i]] = true;
}
for (var j in tmpHash) {
count++;
}
return count;
};
// Map関数
// thisは個々のドキュメントを指す
var map = function(){
var key = getYMD(this.timestamp);
var value = { users:this.user_id};
emit(key, value);
};
// Reduce関数
// 引数valuesには、上記Mapでemitに渡したオブジェクトのリストが来ると考えて差し支えない
var reduce = function(key,values) {
var ret = {users:[]};
values.forEach(function(value){
ret.users.push(value.users);
});
return ret;
};
// reduceの結果を見やすく変形する
var finalize = function(key,value) {
var ret = {};
ret.uu = uniqCount(value.users); // UU数を数える
ret.pv = value.users.length || 0; // PV数を数える
return ret;
};
これで準備OKです。では、定義した関数オブジェクトたちを使ってmapReduceを実行します。
> db.userlog.mapReduce(map,reduce, {finalize:finalize,scope: {getYMD:getYMD, uniqCount:uniqCount }, out:{inline:1}});
{
"results" : [
{
"_id" : "2013-01-23",
"value" : {
"uu" : 1,
"pv" : 4
}
},
{
"_id" : "2013-01-28",
"value" : {
"uu" : 1,
"pv" : 2
}
},
{
"_id" : "2013-04-04",
"value" : {
"uu" : 1,
"pv" : 6
}
},
[中略]
],
"timeMillis" : 276,
"counts" : {
"input" : 320,
"emit" : 320,
"reduce" : 25,
"output" : 28
},
"ok" : 1,
}
とこんな感じで結果が出ます。解説
ポイントの1つ目は、Mapでemitするときのvalueをvar value = { users:this.user_id};
のような形にしておくことです。usersという名前なのに単一のユーザIDが与えられている点が気持ち悪いですが、こうしないと動かないのでこういうものだと思ってください。
ポイント2つ目は、Reduceの中でそのuser_idを集めて配列を作ることです。
var reduce = function(key,values) {
var ret = {users:[]};
values.forEach(function(value){
ret.users.push(value.users);
});
return ret;
};
value.usersは上で説明したように実際には単一の値が入ってきます。よってreduce処理内では、
ret.users.push(1);
ret.users.push(2);
ret.users.push(3);
ret.users.push(2);
のようにユーザIDが次々にpushされます。このpushはドキュメントの数だけ行われますので、最終的に
ret = {users:[1,2,3,2,4,2,1,4,..]};
のようなデータ構造が作られます。このリストの要素数を数えると1日あたりのPV数になり、ユニーク要素数を数えると1日あたりのユニークユーザ数になります。
この数え上げ処理をfinalizeでやれば、お望みの出力が得られます。
よくMapReduceの解説記事で「Mapのemitの出力とReduceの戻り値は同じデータ型にしなければいけない」みたいな解説をみかけますが、全く同じ型でなくても大丈夫みたいです。
(オブジェクトのキーが合ってれば良いっぽい?)
以上、MapReduceによるUU,PVの集計テクニックでした。
あわせて読みたい
カテゴリ:
MongoDB