MongoDBのMapReduceを使ってアクセス解析、ちょっと複雑編


前回はstatusをカウントするだけの単純な物
実際は、もっと複雑
けどいきなり複雑にしすぎてもついていけない。

#### そんな私に！！

日別で、アクセスユーザの何割が、特定の条件を満たしたかをとる処理を
かる〜く書いてみました。

#### 特定の条件って、またむずかしいんじゃね？

その条件は

http status の 200と300を発生させ、尚且つ500は発生させていないユーザである事。

コレを今回の解析内容としてみます。
お題を考えるのがめんどかった（自分用なので）で、一旦これで

要は
ユーザ1が1日に発生させたstatusが200と300だけであれば合格
ユーザ2が1日に発生させたstatusが200と300と500であれば不合格

とりあえず、データの準備

```bash
db.access.save({user_id:'A001',status:200,create_dt:ISODate("2013-06-10T15:00:00Z")});
db.access.save({user_id:'A001',status:300,create_dt:ISODate("2013-06-10T15:00:00Z")});
db.access.save({user_id:'A002',status:200,create_dt:ISODate("2013-06-10T15:00:00Z")});
db.access.save({user_id:'A002',status:300,create_dt:ISODate("2013-06-10T15:00:00Z")});
db.access.save({user_id:'A002',status:500,create_dt:ISODate("2013-06-10T15:00:00Z")});
db.access.save({user_id:'A003',status:200,create_dt:ISODate("2013-06-10T15:00:00Z")});
db.access.save({user_id:'A003',status:300,create_dt:ISODate("2013-06-10T15:00:00Z")});

db.access.save({user_id:'A001',status:200,create_dt:ISODate("2013-06-11T15:00:00Z")});
db.access.save({user_id:'A002',status:200,create_dt:ISODate("2013-06-11T15:00:00Z")});
db.access.save({user_id:'A003',status:200,create_dt:ISODate("2013-06-11T15:00:00Z")});
db.access.save({user_id:'A004',status:200,create_dt:ISODate("2013-06-11T15:00:00Z")});
db.access.save({user_id:'A004',status:300,create_dt:ISODate("2013-06-11T15:00:00Z")});
db.access.save({user_id:'A005',status:200,create_dt:ISODate("2013-06-11T15:00:00Z")});
db.access.save({user_id:'A005',status:300,create_dt:ISODate("2013-06-11T15:00:00Z")});
db.access.save({user_id:'A006',status:200,create_dt:ISODate("2013-06-11T15:00:00Z")});
db.access.save({user_id:'A006',status:300,create_dt:ISODate("2013-06-11T15:00:00Z")});
db.access.save({user_id:'A007',status:200,create_dt:ISODate("2013-06-11T15:00:00Z")});
db.access.save({user_id:'A007',status:300,create_dt:ISODate("2013-06-11T15:00:00Z")});
db.access.save({user_id:'A007',status:500,create_dt:ISODate("2013-06-11T15:00:00Z")});

db.access.save({user_id:'A001',status:200,create_dt:ISODate("2013-06-12T15:00:00Z")});
db.access.save({user_id:'A002',status:200,create_dt:ISODate("2013-06-12T15:00:00Z")});

db.access.save({user_id:'A001',status:200,create_dt:ISODate("2013-06-13T15:00:00Z")});
db.access.save({user_id:'A001',status:300,create_dt:ISODate("2013-06-13T15:00:00Z")});
db.access.save({user_id:'A002',status:200,create_dt:ISODate("2013-06-13T15:00:00Z")});
db.access.save({user_id:'A002',status:300,create_dt:ISODate("2013-06-13T15:00:00Z")});
```

この４日間のアクセスログ（もどき）を使って解析処理を行います。

```test.js
var colName = 'access.status';

var map = function() {
  var status = this.status;
  var time = Date.parse(this.create_dt);
  var dt = new Date();
  dt.setTime(time);

  var label = dt.getFullYear().toString() +
              '-' + 
              dt.getMonth().toString() +
              '-' +
              dt.getDate().toString();

  emit(
    label,
    {user_id: this.user_id, status: status}
  );
}

//1日に置ける200の投稿率
var reduce = function(key, values) {
  var users = {};

  var result = {n: 0,count: 0};

  values.forEach(function(value) {
    if (!users[value.user_id]) {
      users[value.user_id] = [];
    }

    users[value.user_id].push(value.status);
  });

  for (var user_key in users) {
    var statuses = users[user_key];
    var flg_200 = false;
    var flg_300 = false;
    var flg_500 = false;

    result.n ++;

    for (var status_key in statuses) {
      var status = statuses[status_key];
      if (status == 200) {
        flg_200 = true;
      }
      if (status == 300) {
        flg_300 = true;
      }
      if (status == 500) {
        flg_500 = true;
      }
    };


    //200:true, 300:true, 500:false
    if (flg_200 && flg_300 && !flg_500) {
      result.count ++;
    }
  };

  return result; 
}

var finalize = function(key, value) {
  return {
          'ユーザ数': value.n,
          '合格数': value.count,
          '合格率': (value.count / value.n * 100) + '%'
  };
}


mongo = new Mongo('localhost');
mydb = mongo.getDB('nginx');

var res = mydb.access.mapReduce(map, reduce, {finalize: finalize,out: colName});

shellPrint(res);
```
はい、完了では

```bash
$ mongo test.js
$ mongo
```

```mongo
> use nginx
switched to db nginx
> db.access.status.find();
{ "_id" : "2013-5-11", "value" : { "ユーザ数" : 3, "200:true, 300:true, 500:falseを通過した人数" : 2, "ユーザ数における通過率" : "66.66666666666666%" } }
{ "_id" : "2013-5-12", "value" : { "ユーザ数" : 7, "200:true, 300:true, 500:falseを通過した人数" : 3, "ユーザ数における通過率" : "42.857142857142854%" } }
{ "_id" : "2013-5-13", "value" : { "ユーザ数" : 2, "200:true, 300:true, 500:falseを通過した人数" : 0, "ユーザ数における通過率" : "0%" } }
{ "_id" : "2013-5-14", "value" : { "ユーザ数" : 2, "200:true, 300:true, 500:falseを通過した人数" : 2, "ユーザ数における通過率" : "100%" } }
```

決行色々出来そうだと思いました。
近いうちにnginxの生ログをなんかしら解析してまとめてみます。