【问题标题】:MongoDB, MapReduce and sortingMongoDB、MapReduce 和排序
【发布时间】:2012-08-18 01:25:30
【问题描述】:

我可能对此有点不知所措,因为我仍在学习 MongoDB 的来龙去脉,但这里继续。

现在我正在开发一种工具来搜索/过滤数据集,按任意数据点(例如流行度)对其进行排序,然后按 id 对其进行分组。我认为我能做到这一点的唯一方法是通过 Mongo 的 MapReduce 功能。

我不能使用 .group(),因为我正在使用超过 10,000 个键,而且我还需要能够对数据集进行排序。

我的 MapReduce 代码运行良好,除了一件事:排序。排序根本不想工作。

db.runCommand({
  'mapreduce': 'products',
  'map': function() {
    emit({
      product_id: this.product_id,
      popularity: this.popularity
    }, 1);
  },
  'reduce': function(key, values) {
    var sum = 0;
    values.forEach(function(v) {
      sum += v;
    });

    return sum;  
  },
  'query': {category_id: 20},
  'out': {inline: 1},
  'sort': {popularity: -1}
});

我已经在流行度数据点上有一个降序索引,所以它肯定因为缺少它而不起作用:

{ 
  "v" : 1, 
  "key" : { "popularity" : -1 }, 
  "ns" : "app.products", 
  "name" : "popularity_-1" 
}

我只是不明白为什么它不想排序。

我不能将结果集内联,而是将其输出到另一个集合,然后在其上运行 .find().sort({popularity: -1}),因为此功能的工作方式。

【问题讨论】:

  • 你考虑过使用2.2和聚合框架吗?
  • 内联 Map/Reduce 目前不支持排序(请参阅 SERVER-3973 以观看/投票对此功能)。内联 Map/Reduce(或聚合查询)也被限制为最大文档大小(目前 MongoDB 2.0 中为 16Mb)。阻止输出到可以按排序顺序查询的临时集合的问题是什么?另一种选择是在您的客户端应用程序中对结果进行排序,例如使用usort()
  • @Aysa:我有,但是 2.2 还没有准备好生产。
  • @Stennie:似乎是这样。阻止我将其输出到临时集合的问题是我不能让这个功能为进入应用程序的每个请求创建一个新集合。 Mongo 文档读起来不像我可以创建一个“临时”集合。当然,我可以在完成后删除它,但这似乎不是一个很好的解决方案。而且我完全不担心 16MB 的文档大小限制。
  • @JonUrsenbach:本周发布了 MongoDB 2.2 的第二个候选版本(rc1,继 7 月中旬的 rc0)。我预计生产版本不远了。值得测试。

标签: mongodb mongodb-php mongodb-query


【解决方案1】:

首先,Mongo map/reduce 不是为用作查询工具而设计的(就像在 CouchDB 中一样),它是为您运行后台任务而设计的。我在工作中使用它来分析流量数据。

但是,您做错的事情是您将 sort() 应用于您的输入,但这没有用,因为当 map() 阶段完成时,中间文档将按每个 keys 排序。因为您的密钥是一个文档,所以它按product_idpopularity 排序。

这就是我生成数据集的方式

function generate_dummy_data() {
    for (i=2; i < 1000000; i++) { 
        db.foobar.save({
          _id: i, 
         category_id: parseInt(Math.random() * 30), 
         popularity:    parseInt(Math.random() * 50)
        }) 
    }
}

这是我的 map/reduce 任务:

var data = db.runCommand({
  'mapreduce': 'foobar',
  'map': function() {
    emit({
      sorting: this.popularity * -1,
      product_id: this._id,
      popularity: this.popularity,
    }, 1);
  },
  'reduce': function(key, values) {
    var sum = 0;
    values.forEach(function(v) {
      sum += v;
    });

    return sum;  
  },
  'query': {category_id: 20},
  'out': {inline: 1},
});

这是最终结果(很长在这里粘贴):

http://cesarodas.com/results.txt

这很有效,因为现在我们按sorting, product_id, popularity 排序。您可以随心所欲地进行排序,只要记住最终排序是由 key 决定的,无论您的输入是如何排序的。

无论如何,正如我之前所说,您应该避免使用 Map/Reduce 进行查询,它是为后台处理而设计的。如果我是你,我会设计我的数据,以便我可以通过简单的查询访问它,在这种情况下,总是需要权衡复杂的插入/更新来进行简单的查询(这就是我对 MongoDB 的看法)。

【讨论】:

  • 很好的解决方法.. 我没有意识到即使没有明确的sort 设置,发出键也会保持排序顺序:)。想知道这是否是意外的副作用; Map/Reduce docs 实际上提到 sort 键为“对优化有用,例如按发射键排序以减少减少”。
  • 这不是 Mongo 功能,它是适用于算法的 map/reduce 功能,它必须按键排序(因此适用于 CouchDB、Hadoop、等等)。我学会了观看 Cloudera 的一些视频。据我所知,mongo 的 sort 只是对输入进行排序而不是对输出进行排序
  • 这很好用。不确定是否可以再次重组这些数据,所以看起来我只需要使用 M/R 就可以了,直到 2.2 稳定并准备好生产并使用新的聚合框架重新访问。再次感谢,crodas。
  • @crodas 实际上对输入进行排序可能有利于 MR 性能this post discuss about it,我在我的分析数据库中进行了测试,它确实有帮助(可能不是文章所述的 6 倍)。干杯。
【解决方案2】:

正如对原始问题的讨论所述:

  • 具有内联输出的 Map/Reduce 当前无法使用显式 sort 键(请参阅 SERVER-3973)。可能的解决方法包括依赖发出的密钥顺序(请参阅@crodas 的答案);输出到集合并按排序顺序查询该集合;或使用usort()之类的方式对应用程序中的结果进行排序。

  • OP 的偏好是内联结果,而不是创建/删除临时集合。

  • MongoDB 2.2 中的Aggregation Framework(目前是生产候选版本)将提供合适的解决方案。

这是一个与原始 Map/Reduce 类似的查询示例,但使用的是聚合框架:

db.products.aggregate(
  { $match: { category_id: 20 }},
  { $group : {
     _id : "$product_id",
     'popularity' : { $sum : "$popularity" },
  }},
  { $sort: { 'popularity': -1 }}
)

.. 和示例输出:

{
    "result" : [
        {
            "_id" : 50,
            "popularity" : 139
        },
        {
            "_id" : 150,
            "popularity" : 99
        },
        {
            "_id" : 123,
            "popularity" : 55
        }
    ],
    "ok" : 1
}

【讨论】:

  • 暂时使用 Crodas 的解决方案,直到 2.2 准备好生产,然后使用聚合框架重新访问。再次感谢。
猜你喜欢
  • 2015-09-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-08-28
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多