我在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计算出新的/删除的标签?
答案 0 :(得分:1)
正如昵称正确陈述的那样,解决方案将是一个聚合。虽然我会用一个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})
好的,这是交易。首先,我们展开标签数组,按单个标签对其进行分组,并为相应标签的每次出现增加分数。接下来,lastRun
集合中的upsert tagStats
,我们可以做到,因为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();
答案 1 :(得分:0)
回答你的问题: