【问题标题】:MongoDB: Terrible MapReduce PerformanceMongoDB:糟糕的 MapReduce 性能
【发布时间】:2011-04-26 06:17:22
【问题描述】:

我在关系数据库方面有着悠久的历史,但我是 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 年 7 月 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
}

现在真正的肉和土豆来了……我的 map 和 reduce 函数。回到 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 自动完成的。

【问题讨论】:

  • Gah.. 刚刚意识到结果不正确的一个原因。我应该按“价值”而不是“点击”排序。
  • 一个问题是当你将数据导入Mongo时,'day'值是一个巨大的字符串,但在mysql中,它是一个日期(整数)。将数据放入 mongo 时,请确保将其存储为 Date 类型。
  • 您也可以将日期和时间字段分开,并将日期存储为字符串“20110101”或整数20110101,并根据日期进行索引

标签: mongodb mapreduce nosql


【解决方案1】:

您是否已经尝试过使用 mongodb 的 hadoop 连接器?

在此处查看此链接:http://docs.mongodb.org/ecosystem/tutorial/getting-started-with-hadoop/

由于您只使用 3 个分片,我不知道这种方法是否会改善您的情况。

【讨论】:

    【解决方案2】:

    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 的目的。我认为它被用来比替代品更快地处理大量数据。我想我现在看到更多的是处理大量数据的能力,否则这些数据在单台机器上是不可能处理的,而且速度不是一个因素。
    • @mellowsoon,当然,mapreduce 的目的是快速处理大量数据。只是 MongoDB 的实现速度不是很快。
    • Hadoop 非常适合这个;如果您不喜欢他们的 Java 界面,您可以使用 Hadoop 流以其他编程语言编写 map/reduce。 Hadoop 与它一样具有并行化/可扩展性,您可以通过添加更多硬件使其“更快”。
    • MongoDB 中的 MapReduce 实现显然与 map reduce 几乎没有关系。因为就我阅读的所有内容而言,它是单线程的,而 map-reduce 旨在在集群上高度并行使用。
    • 我认为参数应该命名为“out”,而不是“output”,根据docs.mongodb.org/manual/applications/map-reduce
    【解决方案3】:

    也许我来晚了,但是……

    首先,您正在查询集合以填充没有索引的 MapReduce。你应该在“天”创建一个索引。

    MongoDB MapReduce 在单个服务器上是单线程的,但在分片上并行化。 mongo 分片中的数据按分片键排列在连续的块中。

    由于您的分片键是“day”,并且您正在对其进行查询,因此您可能只使用了三台服务器中的一台。分片键仅用于传播数据。 Map Reduce 将使用每个分片上的“天”索引进行查询,并且会非常快。

    在 day key 前面添加一些东西来传播数据。用户名可能是一个不错的选择。

    这样 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 要求您确保您的索引可以保存在内存中。运行 db.views.stats() 会告诉您索引大小。这可以帮助您优化和最大化性能。
    【解决方案4】:

    你没有做错任何事。 (除了您在 cmets 中已经注意到的错误值排序之外。)

    MongoDB 的 map/reduce 性能并没有那么好。这是一个已知的问题;例如,请参见 http://jira.mongodb.org/browse/SERVER-1197,其中幼稚的方法比 M/R 快约 350 倍。

    不过,一个优点是您可以使用mapReduce 调用的out 参数指定永久输出集合名称。一旦 M/R 完成,临时集合将自动重命名为永久名称。这样您就可以安排统计更新并实时查询 M/R 输出集合。

    【讨论】:

    • 感谢您的回复。我将把这个问题搁置一会儿,看看其他人是否有一些意见。不过这真的很令人失望。我想知道瓶颈在哪里?也许是因为 MongoDB 是单线程的,所以协调所有分片的服务器只能走这么快?我也很好奇结果。显示映射的所有 1000 万个文档,而大多数文档本应被查询排除。
    • @mellowsoon:通过对具有相同参数的集合进行计数来验证您的查询(请记住,JS Date 对象的月份是从零开始索引的)。
    • 谢谢,我现在正在这样做。我已经在 3 台服务器上完成了 Mongo 的全新安装,现在我正在导入数据。完成后,我将查看数据在分片之间的分布情况,并选择一个日期范围,该日期范围应将一半的匹配文档放在每个分片上。
    • 只是想添加一个 P.S.:WTF 从零开始的月份?!
    猜你喜欢
    • 2021-04-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-02-04
    • 1970-01-01
    • 1970-01-01
    • 2015-11-23
    • 2013-05-09
    相关资源
    最近更新 更多