MongoDB:可怕的MapReduce性能

我在关系型数据库方面有很长的历史,但是我是MongoDB和MapReduce的新手,所以我几乎是积极的,我一定是做错了。 我会直接回答这个问题。 对不起,如果它很长。

我在MySQL中有一个数据库表,它跟踪每天的成员configuration文件视图的数量。 为了testing它有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以来,我使用此查询来获得排名前五的最受欢迎的个人资料。

 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 } 

现在来了真正的肉和土豆在这里…我的地图和减lessfunction。 回到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上的某人提到在day字段中添加索引,但据我所知,这是由MongoDB自动完成的。

摘自O'Reilly的MongoDB权威指南:

使用MapReduce的代价是速度:组不是特别快,但是MapReduce比较慢,不应该被“实时”使用。你运行MapReduce作为后台作业,创build一个结果集合,然后你可以实时查询该集合。

 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。 你应该创build一个“天”的索引。

MongoDB MapReduce在单个服务器上是单线程的,但在分片上是并行的。 mongo分片中的数据一起保存在由分片键sorting的连续块中。

由于分片密钥是“日”,而您正在查询,您可能只使用三台服务器中的一台。 分片键仅用于传播数据。 Map Reduce将使用每个分片上的“day”索引进行查询,并且速度非常快。

在一天之前添加一些密钥来传播数据。 用户名可以是一个不错的select。

这样Map Reduce将会在所有服务器上启动,并希望将时间减less三次。

像这样的东西:

 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任务,并使用结果集合。

你没有做错什么。 (除了在你的评论中已经注意到的错误价值sorting。)

MongoDB映射/降低性能并不是那么好。 这是一个已知的问题; 例如参见http://jira.mongodb.org/browse/SERVER-1197 ,其中一个简单的方法比M / R快350倍。

一个好处是,你可以用mapReduce调用的out参数指定一个永久的输出集合名称。 一旦M / R完成,临时收集将以primefaces方式重命名为永久名称。 这样,您可以安排您的统计更新和实时查询M / R输出集合。

你已经尝试使用hadoop连接器为mongodb?

看看这个链接: http : //docs.mongodb.org/ecosystem/tutorial/getting-started-with-hadoop/

既然你只用了3个碎片,我不知道这个方法是否会改善你的情况。