与数组聚合时如何提高可怕的MongoDB查询性能

时间:2019-09-09 09:18:38

标签: mongodb mongodb-query aggregation-framework

我有一个数据架构,该架构包含许多分配给实体的更新(每个实体数十万个以上)。我用每个实体的单个顶级文档以及每个实体下的一系列更新来表示这一点。这些顶级文档的架构如下所示:

{
  "entity_id": "uuid",
  "updates": [
    { "timestamp": Date(...), "value": 10 },
    { "timestamp": Date(...), "value": 11 }
  ]
}

我正在尝试创建一个查询,该查询返回过去n个小时内收到更新的实体的数量。 updates数组中的所有更新都可以通过我的应用程序进行更新的方式得到保证。为此,我创建了以下聚合:

db.getCollection('updates').aggregate([
  {"$project": {last_update: {"$arrayElemAt": ["$updates", -1]}}},
  {"$replaceRoot": {newRoot: "$last_update"}},
  {"$match": {timestamp: {"$gte": new Date(...)}}},
  {"$count": "count"}
])

由于某种原因,我不理解,我刚刚粘贴的查询花费了荒谬的时间来完成。实际上,它耗尽了我使用的客户端的15秒超时。

从时间复杂度的角度来看,此查询看起来非常便宜(这是我以这种方式设计此架构的方式的一部分)。相对于集合中被过滤掉的顶级文档的总数,它看起来是线性的,少于10,000。

令人困惑的部分是,这似乎并不是昂贵的$project步骤。如果我一个人运行,查询将在2秒内完成。但是,仅添加$match步骤会使它超时,并在运行数据库的服务器上显示大量CPU和IO使用情况。我最好的猜测是由于某种原因,它会对完整的更新数组执行某些操作,这没有任何意义,因为第一步明确将其限制为仅最后一个元素。

有什么办法可以改善此聚合的性能?像这样以某种方式将所有更新包含在单个数组中是否会导致Mongo无法创建最佳查询,即使数组访问模式本身很有效?

最好做我以前做的事,并将每个更新存储为标有其父实体ID的顶级文档吗?这是我之前所做的,但是性能相当差,我认为我会尝试改用此架构以改进它。到目前为止,这种体验与我期望/期望的相反。

2 个答案:

答案 0 :(得分:0)

使用索引,它将提高查询的性能。

https://docs.mongodb.com/manual/indexes/

为此,请使用mongo指南针检查最常使用哪个索引,然后逐一索引以提高索引的性能。

之后,最后获取您需要的字段,并进行聚合投影。

我希望这可以解决您的问题。但是我建议先进行索引。如果要提取大量数据,这是一个巨大的优势。

答案 1 :(得分:0)

您需要使用索引来支持查询,并尽可能简化索引。

您要查询timestamp字段第一个元素的updates字段,因此为此添加索引:

db.updates.createIndex({'updates.0.timestamp': 1})

您只是在寻找一个计数,因此直接获取该计数:

db.updates.count({'updates.0.timestamp': {$gte: new Date(...)}})