我对关系数据库有很长的历史,但我是MongoDB和MapReduce的新手,所以我几乎肯定我一定做错了.我会直接进入问题.对不起,如果它很长.
我在MySQL中有一个数据库表,用于跟踪每天的成员个人资料视图的数量.对于测试,它有10,000,000行.
CREATE TABLE `profile_views` ( `id` int(10) unsigned NOT NULL auto_increment, `username` varchar(20) NOT NULL, `day` date NOT NULL, `views` int(10) unsigned default '0', PRIMARY KEY (`id`), UNIQUE KEY `username` (`username`,`day`), KEY `day` (`day`) ) ENGINE=InnoDB;
典型数据可能如下所示.
+--------+----------+------------+------+ | id | username | day | hits | +--------+----------+------------+------+ | 650001 | Joe | 2010-07-10 | 1 | | 650002 | Jane | 2010-07-10 | 2 | | 650003 | Jack | 2010-07-10 | 3 | | 650004 | Jerry | 2010-07-10 | 4 | +--------+----------+------------+------+
自2010-07-16以来,我使用此查询获得前5个查看次数最多的个人资料.
SELECT username, SUM(hits) FROM profile_views WHERE day > '2010-07-16' GROUP BY username ORDER BY hits DESC LIMIT 5\G
此查询在一分钟内完成.不错!
现在进入MongoDB的世界.我使用3台服务器设置了分片环境.服务器M,S1和S2.我使用以下命令来设置装备(注意:我已经模糊了IP地址).
S1 => 127.20.90.1 ./mongod --fork --shardsvr --port 10000 --dbpath=/data/db --logpath=/data/log S2 => 127.20.90.7 ./mongod --fork --shardsvr --port 10000 --dbpath=/data/db --logpath=/data/log M => 127.20.4.1 ./mongod --fork --configsvr --dbpath=/data/db --logpath=/data/log ./mongos --fork --configdb 127.20.4.1 --chunkSize 1 --logpath=/data/slog
一旦启动并运行,我就跳上服务器M,然后启动mongo.我发出了以下命令:
use admin db.runCommand( { addshard : "127.20.90.1:10000", name: "M1" } ); db.runCommand( { addshard : "127.20.90.7:10000", name: "M2" } ); db.runCommand( { enablesharding : "profiles" } ); db.runCommand( { shardcollection : "profiles.views", key : {day : 1} } ); use profiles db.views.ensureIndex({ hits: -1 });
然后我从MySQL导入了相同的10,000,000行,这给了我看起来像这样的文档:
{ "_id" : ObjectId("4cb8fc285582125055295600"), "username" : "Joe", "day" : "Fri May 21 2010 00:00:00 GMT-0400 (EDT)", "hits" : 16 }
现在来了真正的肉和土豆...我的地图和减少功能.回到shell中的服务器M我设置查询并像这样执行它.
use profiles; var start = new Date(2010, 7, 16); var map = function() { emit(this.username, this.hits); } var reduce = function(key, values) { var sum = 0; for(var i in values) sum += values[i]; return sum; } res = db.views.mapReduce( map, reduce, { query : { day: { $gt: start }} } );
这是我遇到的问题.此查询需要15分钟才能完成!MySQL查询花了不到一分钟.这是输出:
{ "result" : "tmp.mr.mapreduce_1287207199_6", "shardCounts" : { "127.20.90.7:10000" : { "input" : 4917653, "emit" : 4917653, "output" : 1105648 }, "127.20.90.1:10000" : { "input" : 5082347, "emit" : 5082347, "output" : 1150547 } }, "counts" : { "emit" : NumberLong(10000000), "input" : NumberLong(10000000), "output" : NumberLong(2256195) }, "ok" : 1, "timeMillis" : 811207, "timing" : { "shards" : 651467, "final" : 159740 }, }
它不仅需要永远运行,而且结果甚至看起来都不正确.
db[res.result].find().sort({ hits: -1 }).limit(5); { "_id" : "Joe", "value" : 128 } { "_id" : "Jane", "value" : 2 } { "_id" : "Jerry", "value" : 2 } { "_id" : "Jack", "value" : 2 } { "_id" : "Jessy", "value" : 3 }
我知道那些价值数字应该更高.
我对整个MapReduce范例的理解是执行此查询的任务应该在所有分片成员之间进行分割,这应该会提高性能.我等到Mongo完成导入后在两个分片服务器之间分发文件.当我开始此查询时,每个文档几乎都有5,000,000个文档.
所以我一定做错了.任何人都可以给我任何指示吗?
编辑:IRC上有人提到在日期字段中添加索引,但据我所知,这是由MongoDB自动完成的.
摘自O'Reilly的MongoDB权威指南:
使用MapReduce的价格是速度:组不是特别快,但MapReduce较慢,不应该"实时"使用.你运行MapReduce作为后台作业,它创建一个结果集合,然后你可以实时查询该集合.
options for map/reduce: "keeptemp" : boolean If the temporary result collection should be saved when the connection is closed. "output" : string Name for the output collection. Setting this option implies keeptemp : true.
也许我来不及,但......
首先,您正在查询集合以在没有索引的情况下填充MapReduce.你应该在"日"创建一个索引.
MongoDB MapReduce在单个服务器上是单线程的,但在分片上并行化.mongo分片中的数据以连续的块的形式保存在一起,这些块按分片键排序.
由于您的分片键是"天",并且您正在查询它,您可能只使用三台服务器中的一台.Sharding键仅用于传播数据.Map Reduce将使用每个分片上的"day"索引进行查询,并且速度非常快.
在日期键前添加一些内容以传播数据.用户名可能是一个不错的选择.
这样,Map reduce将在所有服务器上启动,并希望将时间减少三个.
像这样的东西:
use admin db.runCommand( { addshard : "127.20.90.1:10000", name: "M1" } ); db.runCommand( { addshard : "127.20.90.7:10000", name: "M2" } ); db.runCommand( { enablesharding : "profiles" } ); db.runCommand( { shardcollection : "profiles.views", key : {username : 1,day: 1} } ); use profiles db.views.ensureIndex({ hits: -1 }); db.views.ensureIndex({ day: -1 });
我认为通过这些新增功能,您可以更快地匹配MySQL速度.
另外,最好不要实时使用它.如果您的数据不需要"精确"精确,那么现在每次都要执行map reduce任务并使用结果集合.
你没有做错任何事.(除了你在评论中已经注意到的错误值的排序.)
MongoDB映射/降低性能并不是那么好.这是一个已知的问题; 例如http://jira.mongodb.org/browse/SERVER-1197,其中一种天真的方法比M/R快约350倍.
但有一个优点是您可以使用调用的out
参数指定永久输出集合名称mapReduce
.完成M/R后,临时收集将以原子方式重命名为永久名称.这样,您可以安排统计更新并实时查询M/R输出集合.