在没有MapReduce的情况下在MongoDB中应用函数和排序

时间:2012-02-09 14:38:04

标签: mongodb

我有一个有趣的问题。我有一个工作的M / R版本,但它在小规模环境中并不是真正可行的解决方案,因为它太慢而且查询需要实时执行。

我想迭代集合中的每个元素并对其进行评分,按降序排序,限制为前10并将结果返回给应用程序。

这是我想用伪代码应用于每个文档的函数。

var score = 0;
foreach(tag in document.Tags) {
    score += someMap[tag];
}
return score;

1 个答案:

答案 0 :(得分:3)

由于您的someMap每次都在变化,除了对所有文档进行评分并返回得分最高的文档之外,我看不到任何其他选择。无论您采用何种方法进行此类操作,您都必须考虑集合中的所有文档,这些文档速度会很慢,并且随着您扫描的集合的增长而变得越来越昂贵。

map reduce的一个问题是每个mongod实例只能运行一个并发映射reduce。这是javascript引擎的限制,它是单线程的。多个映射reduce将被交错,但它们不能彼此同时运行。这意味着,如果您依赖map reduce进行“实时”使用,也就是说,如果您的网页必须运行地图缩小以进行渲染,那么您最终将达到限制,其中页面加载时间会变得无法接受。

您可以通过查询应用程序中的所有文档,以及对应用程序代码进行评分,排序和限制来解决此问题。与map reduce不同,MongoDB中的查询可以同时运行,但这当然意味着您的应用程序服务器必须做很多工作。

最后,如果您愿意等待MongoDB 2.2发布(应该在几个月内),您可以使用新的aggregation framework代替map reduce。您必须按下someMap以生成正确的管道步骤。以下是someMap{"a": 5, "b": 2}时的情况示例:

db.runCommand({aggregate: "foo",
    pipeline: [
        {$unwind: "$tags"},
        {$project: {
            tag1score: {$cond: [{$eq: ["$tags", "a"]}, 5, 0]},
            tag2score: {$cond: [{$eq: ["$tags", "b"]}, 3, 0]}}
        },
        {$project: {score: {$add: ["$tag1score", "$tag2score"]}}},
        {$group: {_id: "$_id", score: {$sum: "$score"}}},
        {$sort: {score: -1}},
        {$limit: 10}
    ]})

这有点复杂,并且解释说:

  1. 首先,我们“展开”tags数组,以便管道中的以下步骤处理文档“tags”是标量 - 来自数组的标记的值 - 以及所有其他文档字段(特别是_id)对于每个展开的元素都是重复的。
  2. 我们使用投影运算符将标签转换为命名的得分字段。每个粗略的$cond / $eq表达式意味着(对于tag1score示例)“如果'tags'字段中的文档中的值id等于'a',则返回5并将该值分配给新字段tag1score,否则返回0并指定“。对于someMap中的每个标记/分数组合,将重复此表达式。在管道中的这一点上,每个文档将包含N tagNscore个字段,但最多其中一个将具有非零值。
  3. 接下来,我们使用另一个投影运算符来创建score字段,其值是文档中tagNscore字段的总和。
  4. 接下来,我们按照_id对文档进行分组,并在每个组中的所有文档中总结上一步中score字段的值。
  5. 我们按score排序,降序(即最高分)
  6. 我们仅限前10名。
  7. 我将把它作为练习留给读者如何将someMap转换为步骤2中正确的投影集,以及在步骤3中添加的正确字段集。

    这与您的应用程序代码或map reduce会经历的步骤基本相同,但具有以下明显优势:聚合框架不是map reduce,而是完全用C ++实现,并且比map更快,更并发降低;与向应用程序查询所有文档不同,聚合框架与服务器端的数据一起使用,从而节省了网络负载。但与其他两种方法一样,这仍然需要考虑每个文档,并且只能在为所有文档计算得分后限制结果集。