出现问题
公司是做互联网广告投放的,需要统计广告展现量在前五百的域名。最简单粗暴的做法就是group by,根据url分组,然后再sort一下就搞定晒!结果问题就出现了。
如下统计的2015-02-28当日22时的日志,文档数量:904405。
db['log.2015-02-28_22'].group({ key : {domainUrl:1}, initial : {count:0}, reduce : function Reduce(doc, out) { out.count++;}
报错:
Error in executing GroupByCommand 'group' failed: exception: group() can't handle more than 20000 unique keys (response: { "errmsg" : "exception: group() can't handle more than 20000 unique keys", "code" : 17203, "ok" : 0.0 })Type: MongoDB.Driver.MongoCommandException
错误信息不够具体,加之理解问题,我以为是集合中的文档数量不能超过20000,2万都不能支持那要group干嘛。经过多次测试,终于证明了我是错的。是不支持大于2万的结果集,即分组后产生的文档数量不能超过2万。
//集合大小> db['log.2015-02-28_22'].count();904405//唯一url数量> db['log.2015-02-28_22'].distinct('domainUrl').length;20738
这个集合中的唯一url数量是20738,刚好超过了2万,所以MongoDB的group就无能为力了。
另外测试还意外发现,distinct对结果集大小也是有限制的。结果集大小不能超过16Mb。
> db['log.2015-02-28_22'].distinct('webUrl').length; distinct failed: { "errmsg" : "exception: distinct too big, 16mb cap", "code" : 17217, "ok" : 0
MongoDB为什么这么多限制,而且阈值都比较小,还请大神指点。
解决问题
问题是用来解决的,通过stackoverflow大神的指点,我尝试着用MongoDB的mapreduce搞定它,其实一开始我是拒绝的,因为,你不能让我用,我就用;首先,我要看一下我会不会用。结果发现,我不会用,然后看了看MongoDB的说明文档。写出如下代码:
db.runCommand({ mapreduce: "log.2015-02-28_22", map : function Map() { emit( { uuid:this.adUUId, //对不同的广告统计url url:this.domainUrl }, {count: 1} ); }, reduce : function Reduce(key, values) { var total=0; for( var i in values){ total +=values[i].count; } return {count:total}; }, finalize : function Finalize(key, reduced) { return reduced;}, out : { inline : 1 }});
我用了一次,感觉效果还不错。现在也推荐给你试一下。
结果对象的结构如下:
{ "_id" : { "uuid" : "inmobi" , "url" : "static.51y5.net" } , "value" : { "count" : 82409.0}}{ "_id" : { "uuid" : "inmobi" , "url" : "applet.kakamobi.com"} , "value" : { "count" : .23714.0}}
_id属性中是分组字段,value属性中保存结果对象。mongodb聚合函数产生的count是double类型的。你可以在finalize方法中处理一下,转换成整型。我是在java 代码转换的。
MongoDB的Mapreduce我也是第一次用,更具体的用法详解,待我研究之后,再做报告。因为我不愿意写完以后,再加点特技上去,文章“duang”一下,很赞,很劲,这样读者出来一定会骂我,博主根本就是不会,还装逼。(just kidding.)
以上内容有不对的地方,还望大家指正,虚心学习。
Thanks a lot ,END!