加快大型馆藏的汇总

时间:2019-11-29 13:31:46

标签: mongodb mongodb-query aggregation-framework

我目前有一个包含大约270 000 000个文档的数据库。他们看起来像这样:

[{
    'location': 'Berlin',
    'product': 4531,
    'createdAt': ISODate(...),
    'value': 3523,
    'minOffer': 3215,
    'quantity': 7812
},{
    'location': 'London',
    'product': 1231,
    'createdAt': ISODate(...),
    'value': 53523,
    'minOffer': 44215,
    'quantity': 2812
}]

该数据库目前拥有一个多月的数据,并且在大约170个位置(在欧盟和美国)拥有大约8000种产品。这些文档代表了时间步长,因此每个位置每个产品每天大约有12至16个条目(不过每小时最多1个条目)。
我的目标是检索过去7天在给定位置的产品的所有时间步。对于单个位置,此查询使用索引{ product: 1, location: 1, createdAt: -1 }可以合理地快速(150毫秒)运行。

但是,我不仅需要针对单个位置,还需要针对整个区域(大约85个位置)的这些时间步长。我目前正在使用此汇总进行此操作,该汇总每小时将所有条目分组并平均所需的值:

    this.db.collection('...').aggregate([
      { $match: { { location: { $in: [array of ~85 locations] } }, product: productId, createdAt: { $gte: new Date(Date.now() - sevenDaysAgo) } } }, {
        $group: {
          _id: {
            $toDate: {
              $concat: [
                { $toString: { $year: '$createdAt' } },
                '-',
                { $toString: { $month: '$createdAt' } },
                '-',
                { $toString: { $dayOfMonth: '$createdAt' } },
                ' ',
                { $toString: { $hour: '$createdAt' } },
                ':00'
              ]
            }
          },
          value: { $avg: '$value' },
          minOffer: { $avg: '$minOffer' },
          quantity: { $avg: '$quantity' }
        }
      }
    ]).sort({ _id: 1 }).toArray()

但是,即使使用索引{ product: 1, createdAt: -1, location: 1 }(〜40秒),这也确实非常慢。有什么方法可以加快聚合速度,使其最多减少几秒钟?这是否有可能,或者我应该考虑使用其他东西吗?
我曾考虑过将这些聚合保存到另一个数据库中,然后检索并聚合其余的聚合,但是对于网站上第一个必须等​​待40秒才能使用的用户来说,这确实很尴尬。

2 个答案:

答案 0 :(得分:1)

这些想法可以使查询和性能受益。所有这些工具是否可以一起工作取决于一些试验和测试。另外,请注意,更改数据存储方式和添加新索引意味着将对应用程序进行更改(即捕获数据),并且需要仔细验证对同一数据的其他查询(它们不会以错误的方式受到影响) )。


(A)在文档中存储一天的详细信息:

将一天的数据作为一系列子文档存储(嵌入)在同一文档中。每个子文档代表一个小时的条目。

发件人:

{
    'location': 'London',
    'product': 1231,
    'createdAt': ISODate(...),
    'value': 53523,
    'minOffer': 44215,
    'quantity': 2812
}

至:

{
    location: 'London',
    product: 1231,
    createdAt: ISODate(...),
    details: [ { value: 53523, minOffer: 44215, quantity: 2812 }, ... ]
}

这意味着每个文档大约十个条目。为条目添加数据将把数据推送到详细信息数组中,而不是像本申请中那样添加文档。如果需要小时信息(时间),它也可以存储为明细子文档的一部分;这完全取决于您的应用程序需求。

这种设计的好处:

  • 要维护和查询的文档数量将减少(每 每天约十个文件的产品)。
  • 在查询中,分组阶段将消失。这只是一个 项目阶段。请注意,$project支持accumulators $avg$sum

接下来的阶段将创建当天(或文档)的总和和平均值。

{ 
    $project: { value: { $avg: '$value' }, minOffer: { $avg: '$minOffer' }, quantity: { $avg: '$quantity' } }
}

请注意,文档的大小增加不多,每天存储的详细信息量也很大。


(B)按地区查询:

当前多个匹配位置(或区域)与此查询文件管理器:{ location: { $in: [array of ~85 locations] } }匹配。此过滤器显示:location: location-1, -or- location: location-3, -or- ..., location: location-50。添加新字段region,将使用一个匹配的值进行过滤。

按地区查询将更改为:

{ 
  $match: { 
      region: regionId, 
      product: productId, 
      createdAt: { $gte: new Date(Date.now() - sevenDaysAgo) } 
  } 
}

将提供regionId变量以与region字段匹配。

请注意,“按位置”和“按地区”这两个查询都将受益于上述两个注意事项,即 A B


(C)索引注意事项:

当前索引:{ product: 1, location: 1, createdAt: -1 }

考虑到新字段region,将需要更新的索引。没有region字段的索引,带有region的查询将无法受益。需要第二个索引;适合查询的复合索引。使用region字段创建索引意味着写操作的额外开销。此外,将有内存和存储注意事项。

注释:

添加索引后,如果两个查询(“按位置”和“按区域”)正在使用各自的索引,则都需要使用explain进行验证。这将需要一些测试;一个反复试验的过程。

再次,添加新数据,以其他格式存储数据,添加新索引需要考虑以下因素:

  • 仔细测试并验证其他现有查询是否照常执行。
  • 数据捕获需求的变化。
  • 测试新查询并验证新设计是否按预期执行。

答案 1 :(得分:0)

老实说,您的聚合功能已尽可能地优化,尤其是如果您将{ product: 1, createdAt: -1, location: 1 }作为索引,如您声明的那样。

我不确定您的整个产品如何构建,但是我认为最好的解决方案是拥有另一个仅包含上周“相关”文档的收藏集。

然后,您可以轻松查询该集合。使用TTL Index在Mongo中也很容易做到这一点。

如果这不是一个选项,则可以在“相关”文档中添加一个临时字段并对其进行查询,从而使检索它们的速度有所提高,但是维护该字段将需要您每X次运行一个进程,这可能会使您的结果现在100%准确,具体取决于您决定何时运行。