【问题标题】:documents with tags in mongodb: getting tag countsmongodb中带有标签的文档:获取标签计数
【发布时间】:2015-04-11 18:50:36
【问题描述】:

我在 MongoDB 中有一个 collection1 带有标签的文档。标签是嵌入的字符串数组:

{
    name: 'someObj',
    tags: ['tag1', 'tag2', ...]
}

我想知道集合中每个标签的计数。因此我有另一个带有标签计数的collection2

{
    { 
        tag: 'tag1',
        score: 2
    }
    { 
        tag: 'tag2',
        score: 10
    }
}

现在我必须让两者保持同步。从collection1 插入或删除时,这是相当微不足道的。但是,当我更新 collection1 时,我会执行以下操作:

1.) 获取旧文档

var oldObj = collection1.find({ _id: id });

2.) 计算新旧标签数组的差异

var removedTags = $(oldObj.tags).not(obj.tags).get();
var insertedTags = $(obj.tags).not(oldObj.tags).get();

3.) 更新旧文档

collection1.update(
    { _id: id },
    { $set: obj }
);

4.) 更新插入和删除标签的分数

// increment score of each inserted tag
insertedTags.forEach(function(val, idx) {
    // $inc will set score = 1 on insert
    collection2.update(
        { tag: val },
        { $inc: { score: 1 } },
        { upsert: true }
    )
});
// decrement score of each removed tag
removedTags.forEach(function(val, idx) {
    // $inc will set score = -1 on insert
    collection2.update(
        { tag: val },
        { $inc: { score: -1 } },
        { upsert: true }
    )
});

我的问题:

A) 这种单独保存分数簿的方法是否有效?还是有更高效的一次性查询来从 collection1 中获取分数?

B) 即使分开记账是更好的选择:是否可以用更少的步骤完成,例如让 mongoDB 计算哪些标签是新的/删除的?

【问题讨论】:

    标签: mongodb count tags


    【解决方案1】:

    正如 nickmilion 正确指出的那样,解决方案是聚合。虽然我会用 nack 来做:我们会将它的结果保存在一个集合中。将做的是用实时结果换取极速提升。

    我会怎么做

    通常情况下,对实时结果的需求被高估了。因此,我会使用预先计算的标签统计信息,并每 5 分钟左右更新一次。这应该足够好了,因为大多数此类调用都是由客户端异步请求的,因此在必须对特定请求进行计算的情况下,一些延迟可以忽略不计。

    db.tags.aggregate(
      {$unwind:"$tags"},
      {$group: { _id:"$tags", score:{"$sum":1} } },
      {$out:"tagStats"}
    )
    db.tagStats.update(
      {'lastRun':{$exists:true}},
      {'lastRun':new Date()},
      {upsert:true}
    )
    
    db.tagStats.ensureIndex({lastRun:1}, {sparse:true})
    

    好的,这就是交易。首先,我们展开标签数组,按各个标签对其进行分组,并为每个标签的每次出现增加分数。接下来,我们在tagStats 集合中upsertlastRun,我们可以这样做,因为MongoDB 是无模式的。接下来,我们创建一个sparse index,它只保存索引字段存在的文档的值。如果索引已经存在,ensureIndex 是一个非常便宜的查询;但是,由于我们将在代码中使用该查询,因此我们不需要手动创建索引。使用此过程,以下查询

    db.tagStats.find(
     {lastRun:{ $lte: new Date( ISODate().getTime() - 300000 ) } },
     {_id:0, lastRun:1}
    )
    

    变成covered query:从索引中回答的查询,该索引往往驻留在 RAM 中,使该查询闪电般快速(在我的测试中略低于 0.5 毫秒的中位数)。那么这个查询有什么作用呢?当聚合的最后一次运行超过 5 分钟(5*60*1000 = 300000 毫秒)前运行时,它将返回一条记录。当然,您可以根据自己的需要进行调整。

    现在,我们可以总结一下了:

    var hasToRun = db.tagStats.find(
      {lastRun:{ $lte: new Date( ISODate().getTime() - 300000 ) } },
      {_id:0, lastRun:1}
    );
    
    if(hasToRun){
    
      db.tags.aggregate(
        {$unwind:"$tags"},
        {$group: {_id:"$tags", score:{"$sum":1} } },
        {$out:"tagStats"}
      )
    
      db.tagStats.update(
        {'lastRun':{$exists:true}},
        {'lastRun':new Date()},
        {upsert:true}
      );
    
      db.tagStats.ensureIndex({lastRun:1},{sparse:true});
    
    }
    // For all stats
    var tagsStats = db.tagStats.find({score:{$exists:true}});
    // score for a specific tag
    var scoreForTag = db.tagStats.find({score:{$exists:true},_id:"tag1"});
    

    替代方法

    如果实时结果真的很重要,并且您需要所有标签的统计信息,只需使用聚合而不将其保存到另一个集合:

    db.tags.aggregate(
      {$unwind:"$tags"},
      {$group: { _id:"$tags", score:{"$sum":1} } },
    )    
    

    如果您一次只需要一个特定标签的结果,实时方法可能是使用特殊索引,创建覆盖查询并简单地计算结果:

    db.tags.ensureIndex({tags:1})
    var numberOfOccurences = db.tags.find({tags:"tag1"},{_id:0,tags:1}).count();
    

    【讨论】:

    • 谢谢我接受了这个答案,因为它很好地涵盖了另一种方法 - 但是:您在 db.tags 上执行的聚合查询 - 在我的场景中不存在。场景是所有tags 数组都嵌套在父文档中。所以我必须以某种方式展开所有父文档,子展开标签,对它们进行分组等等。你能暗示一下吗?谢谢!
    • db.tags 只是集合的任意名称...尝试使用您的实际集合名称。 ;)
    • 我知道db.tags 是一个任意集合名称——它包含标签。但这不是我的情况。正如我所写:“场景是所有标签数组都嵌套在父文档中。”所以我的名字,用你的名字说,是db.docs.tags。所以我在不同的文档之间分割了标签 - 正如我在上面的初始帖子中所示。
    【解决方案2】:

    回答你的问题:

    • B):你不必自己计算差异use $addToSet
    • A):您可以通过聚合框架结合$unwind 和$count 获取计数

    【讨论】:

    • 首先谢谢!但是:我知道聚合框架,但为此目的它更快/更好吗?我选择的单独收集方法不是更快吗?另外:感谢“addToSet”的提示,但我认为这还不够:它只是附加,但我还需要删除已删除的标签。另外,如果我坚持单独收集方法,那么我需要知道附加了哪些标签。
    • 不,它不会更快:(,它只会让您免于自己保持计数的麻烦,根据集合大小和您执行这些插入的频率,仍然可以是可接受的解决方案。跨度>
    • 你说得对,我的方法有一个严重的缺点:我在每个标签插入/更新上都做了很多额外的工作。但是对于标签计数的每个查询,我可能会更快,这是更常见的操作 - 但这基本上是我的问题之一:当我想知道计数时,我的方法真的更快吗?或者,如果定义了正确的索引,聚合框架版本是否同样快?如何/什么定义为索引?
    • 当然,当您想知道计数时它会更快,无论您使用索引做什么,与 collection2 上的普通查询相比,它永远不会更快
    猜你喜欢
    • 2011-10-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-10-09
    • 2020-10-25
    • 1970-01-01
    • 2018-12-23
    • 1970-01-01
    相关资源
    最近更新 更多