最近使用 mongoDB 存储了大量的用户登录记录,文档结构如下:
{
"_id" : ObjectId("5a1fde257756dca1c86f2d23"),
"_class" : "com.aaa.LoginHistory",
"uid" : "94989242",
"ip" : "192.168.1.219",
"imei" : "865736037366641",
"epid" : "33189023-9472-4f30-81cf-8c7d13132aae5",
"platform" : "Android",
"timestamp" : "1512037117100",
"address" : "中国,江苏省,南京市,建邺区"
}
- 现在有个需求,需要统计每一天( yyyy-MM-dd )的登录用户数( uid 对应用户,每天存在多次登陆记录,即 uid 可能重复)。
- 由于技术菜,选择了比较 low 的方法实现:根据需要查询的时间间隔的时间,就是开始时间戳和结束时间戳,把它分成每天一个间隔,然后利用以下方法去统计:
db.loginHistory.aggregate([
{$match:{"timestamp":{"$lte":"1513412162000","$gte":"1512548162000"}}},
{$group:{_id:"$uid"}}
])
因此出现了很大的问题:
- uid 的重复,导致登录用户数量统计出现重复。
- 如果需要查询一年以内的话,则需分成 365 个时间区不断得循环调用,导致响应时间超级慢。
最后经过各种百度找到了一种实现:
db.loginHistory.aggregate(
[
{
$match:{
timestamp:{
$lte:"1513399696240",
$gte:"1513389696240"
}
}
},
{
$project:{
uid:1,
timestamp:1,
_id:0
}
},
{
$group:{
_id:{
uid:"$uid",
date:{
$dateToString:{
format:"%Y-%m-%d",
date:{
$add:[new Date(0),parseInt("$timestamp")]
}
}
}
}
}
},
{
$group:{
_id:{
date:"$_id.date"
},
count:{"$sum":1}
}
}
]
)
但是问题又来了,执行报错:
assert: command failed: {
"ok" : 0,
"errmsg" : "$dateToString is only defined on year 0-9999, tried to use year 292278994",
"code" : 18537
} : aggregate failed
_getErrorWithCode@src/mongo/shell/utils.js:25:13
doassert@src/mongo/shell/assert.js:13:14
assert.commandWorked@src/mongo/shell/assert.js:287:5
DBCollection.prototype.aggregate@src/mongo/shell/collection.js:1312:5
百思不得其解,尝试性的把 parseInt("$timestamp")改成 parseInt("1513399696240"),执行得到结果:
{ "_id" : { "date" : "2017-12-16" }, "count" : 19 }
经过 v2 上的大哥提建议说用 mapReduce,尝试着改成了 mapReduce,结果成功了,可是:
db.loginHistory.mapReduce(
function(){
var date = new Date(parseInt(this.timestamp));
var dateKey = date.getFullYear()+"-"+(date.getMonth()+1)+"-"+date.getDate();
emit(dateKey,{uid:this.uid,count:1});
},
function(key, values){
var result = 0;
var json = {};
for (var i = 0; i < values.length; i++) {
if(json[values[i].uid] != 1) {
result += values[i].count;
json[values[i].uid] = 1;
}
}
return {count:result};
},
{
query:{
"timestamp":{
"$gte":"1512662400000","$lte":"1512921599999"
},
"epid":"a6c89023-9472-4f30-81cf-8c7dea62aae5",
"platform":"ios"
},
out:"aaa"
}
).find()
109W 条数据,花了 11 秒,没错,就是 11 秒,就一台单机 mongoDB,我觉得用 mapReduce 有意义?
线上大概数据已经超过 5 千万条,如果不用 aggregate 的话,30 秒超时时间都不够。。。