我在关系数据库方面有很长的历史,但我是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自动完成的。
答案 0 :(得分:53)
摘自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.
答案 1 :(得分:27)
也许我来不及,但是......
首先,您要查询集合以在没有索引的情况下填充MapReduce。你应该在“day”上创建一个索引。
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任务并使用结果集合。
答案 2 :(得分:6)
你没有做错任何事。 (除了你在评论中已经注意到的错误值的排序。)
MongoDB映射/降低性能并不是那么好。这是一个已知的问题;例如,参见http://jira.mongodb.org/browse/SERVER-1197,其中天真的方法比M / R快350倍。
但是,一个优点是您可以使用out
调用的mapReduce
参数指定永久输出集合名称。完成M / R后,临时收集将以原子方式重命名为永久名称。这样,您可以安排统计更新并实时查询M / R输出集合。
答案 3 :(得分:0)
您是否已尝试将hadoop连接器用于mongodb?
请点击此链接:http://docs.mongodb.org/ecosystem/tutorial/getting-started-with-hadoop/
由于您只使用了3个分片,我不知道这种方法是否会改善您的情况。