MongoDB计算相关集合中的数百万个文档

时间:2018-12-24 11:30:47

标签: mongodb aggregation-framework

所以,我很受困扰,我在Stackoverflow上有了第一篇文章,经过多年潜伏,我绝对需要一些好的建议。 我有两种文档类型:

文章

今天大约有1万5千篇文章,但是在加入custumer时迅速增加。我们不想在这里限制。

{ 
    "_id" : ObjectId("5bd054d8fd5298d07ddc293a"), 
    "title" : "A neat title"
}

活动

在用户导航的每个与营销相关的阶段(例如: view share 文章),每条文章大约进行1k笔活动。增加网站访问量将增加文章和活动之间的1/1000比率。

{ 
    "_id" : ObjectId("5bbdae8afd529871473c1111"), 
    "article" : ObjectId("5bd054d8fd5298d07ddc293a"), 
    "what" : "view"
}
{ 
    "_id" : ObjectId("5bbdae8afd529871473c2222"), 
    "article" : ObjectId("5bd054d8fd5298d07ddc293a"), 
    "what" : "share"
}

我的目标是汇总计算相关活动的文章:

{ 
    "_id" : ObjectId("5bd054d8fd5298d07ddc293a"), 
    "title" : "A neat title",
    "statistics" : {
        'view':1,
        'share':1,
     }
}

Activity.article和Activity.what上的索引都已设置。

在小型数据集上,我可以通过以下聚合轻松实现我的目标:

db.article.aggregate([
{ $match: { 
    ... some unrelevant match
}},
{ $lookup: {
     from: "activity",
     localField: "_id",
     foreignField: "article",
     as: "activities"
}},
{ $project: {
    data: '$$ROOT',
    views: {$filter: {
        input: '$activities',
        as: 'view',
        cond: {$eq: ['$$what', 'view']}
    }},
    shares: {$filter: {
        input: '$activities',
        as: 'share',
        cond: {$eq: ['$$what', 'share']}
    }}
}},
{ $addFields: {
        'data.statistics.views': { $size: '$views' },
        'data.statistics.shares': { $size: '$shares' }
}},
{ $project: { 
    'data.activities': 0,
    'views': 0,
    'shares': 0
}},
{ $replaceRoot: { newRoot: '$data' } },
])

一旦$ lookup没有超过16MB的限制,这就会给我确切的需求。如果我有数百万个“活动”,则即使文档中指出,聚合也会失败:

  

Aggregation Pipeline Limits该限制仅适用于返回的文档;在管道处理过程中,文档可能会超出此大小

我已经尝试过什么:

  1. 添加allowDiskUse /失败,因为我在数据目录中看不到_tmp文件夹,因此似乎没有写任何内容
  2. 添加allowDiskUse +光标 /也失败
  3. 使用{$ out:“ result”}来保存结果到临时集合中 /失败
  4. 使用 Lookup+Unwind coalescence 更改聚合可以正常运行,但是对于150万活动,结果将在10秒内返回,因为在展开之后,管道的每个阶段(即:组返回以重建文档)不能使用现有索引。
  5. 更改 Lookup using the internal pipelining /可以运行,但是进行20万活动需要1.5分钟(我停止了150万测试),并在6秒这可能是我最好的一匹马...

我什至尝试过这样的事情:

db.article.aggregate([
    { $match: { 
        ...
    }},
    { $addFields: {'statistics.views': db.activity.find({ "article": ObjectId('5bd054d8fd5298d07ddc293a'), "what" : "view" }).count()
])

效果出色(0.008秒/条)。问题是我无法“可变化”该ObjectId:

db.article.aggregate([
    { $match: { 
            ...
    }},
    { $addFields: {

            'statistics.views': db.activity.find({ "article": ObjectId('5bd054d8fd5298d07ddc293a'), "what" : "view" }).count(),
// ^ returns correct count

            'statistics.querystring': { $let: {
            vars:   { articleid: "$_id", whatvalue: 'view' },
            in:     { 'query':{ $concat: [ "db.activity.find( { 'article': ObjectId('", { $toString: "$$articleid" }, "'), 'what' : '", "$$whatvalue", "' } ).count()" ] } }
            }},
// ^ returns correct query to string


            'statistics.variablequery': { $let: {
            vars: { articleid: "$_id", whatvalue: 'view' },
            in:  db.activity.find( { "article": '$$articleid', "what" : "$$whatvalue" } ).count()
            }},
// ^ returns 0

    }}
])

我乐于接受所有解决方案,即使我在编写活动时排除了在我的文章中增加计数器的可能性,也可以更改我的收藏集,因为我需要按日期进行过滤(即:给我最后一个的全部份额)周)

1 个答案:

答案 0 :(得分:1)

活动文档有多大?由于它们看起来很小-我将把活动保留为Article文档中的数组。文档限制为16mb,这样就可以了,您可以避免在磁盘上使用_id和重复的商品ID字段-从而使磁盘上的数据小得多。请记住,MongoDB不是您的传统SQL数据库-嵌入式字段和文档是您的朋友。

如果活动将是无限的(即可以永远增长),那么我建议采用一种分类的方法,即每天每条文章都有一个活动文档,例如:

{ 
    "_id" : {
       "article" : ObjectId("5bbdae8afd529871473c2222"),
       "when": "2018-12-27"
    },
    "activities" : [
       {"what": "view", "when": "12:01"},
       {"what": "share", "when": "13:16"}
    ]
}

您可以在“ when”字段中存储完整的时间戳记或ISODates,但是这种方法在磁盘上可读性更高,并且可能更紧凑。