【问题标题】:MongoDB count millions of documents in related collectionMongoDB 统计相关集合中的数百万个文档
【发布时间】:2019-05-23 14:00:50
【问题描述】:

所以,我被困住了,我在 Stackoverflow 上的第一颗子弹,经过多年的潜伏,我绝对需要一些好的建议。 我有两种文档类型:

文章

今天大约有 15,000 篇文章,但在新客户加入时迅速增加。我们不想在这里设置限制。

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

活动

每篇文章大约有 1k 个活动,在用户导航的每个营销相关阶段编写(例如:viewshare 一篇文章)。为网站带来更多流量将提高文章和活动之间的 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 + cursor / 也会失败
  3. 使用 { $out:"result" } 将结果保存在临时集合中 / 失败
  4. 使用 Lookup+Unwind coalescence 更改聚合/它可以工作,但是对于 150 万个活动,结果会在 10 秒内返回,因为在展开之后,管道的每个阶段(即:组回到重建文档)不能使用现有索引。
  5. 更改 Lookup using the internal pipelining / 它有效.这可能是我最好的马...

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

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

    }}
])

我对所有解决方案持开放态度,即使我在编写活动时排除了在文章中增加计数器的可能性,也可以更改我的收藏,因为我需要按日期过滤(即:给我最后一次的所有份额周)

【问题讨论】:

    标签: mongodb aggregation-framework


    【解决方案1】:

    活动文档有多大?由于它们看起来很小 - 我会将活动作为数组保存在文章文档中。文档限制为 16mb,这样应该没问题,您可以避免磁盘上的 _id 和重复的文章 ID 字段 - 使磁盘上的数据更小。请记住,MongoDB 不是您的传统 SQL 数据库 - 嵌入式字段和文档是您的朋友。

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

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

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

    【讨论】:

    • 也许我错了,但是将“数百万”的活动放在文章的数组中会违反 16 mb 的限制......
    • 我不认为每篇文章有数百万个活动。更像是 100 万篇文章,每篇文章都有 100 个甚至 1000 个活动。没问题。
    • 让我更好地理解:我创建了一篇文章,在其生命周期中,我在子数组中创建了活动。当我达到〜1000个活动时,我用一个空数组复制文章?或者更好的是,每天我都用一个空的“活动”数组复制文章……或者更好的是,我可以创建一个 DailyReport 对象,其中包含活动列表和类型/小时/用户的预计算索引……我在集思广益,但是这似乎是一个很好的解决方案
    • 你描述的叫分桶。查看docs.mongodb.com/manual/applications/data-models。请用近似大小更新您的问题 - 总共有多少篇文章,每篇文章将有多少活动?活动多久添加到现有文章中?
    猜你喜欢
    • 2022-01-23
    • 1970-01-01
    • 2021-05-14
    • 2018-07-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多