如何在使用数组聚合时提高 MongoDB 查询性能

4

我有一个数据模式,其中包含分配给实体的许多更新(每个实体数十万个以上)。我用每个实体的单个顶级文档和其中的更新数组来表示它。这些顶级文档的模式如下:

{
  "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
使用索引,可以提高查询的性能。

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

为此,请使用Mongo Compass检查使用最多的索引,然后逐个对它们进行索引以提高性能。

之后,在聚合中使用投影,仅获取最终需要的字段。

我希望这可以解决您的问题。但是我建议首先进行索引。在大数据获取的情况下,这是一个巨大的优势。


0

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

您正在针对更新字段的第一个元素的时间戳字段进行查询,因此请为其添加索引:

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

你只是想要一个计数,所以直接获取它:

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

嗨!感谢您抽出时间提供答案。我认为这并不完全适用于我所要做的事情。我的用例是将更新按照从旧到新的顺序插入到数组中,因此.0将查看最旧的更新而不是最新的更新。我可以修改填充更新的应用程序代码,使其插入到数组的前面而不是后面,但这似乎是违反直觉的。此外,我按照此处指定的方式创建了索引,但它似乎对我的原始查询性能没有帮助(考虑到其语义,这并不令人惊讶)。 - Ameo
我刚意识到我在示例中输入了 0 而不是 -1...非常抱歉。我已经更正了示例。 - Ameo
如果您不想更改“更新”中元素的顺序,则保留一个单独的字段来包含最新更新的时间戳,并在更新时将其设置为该值。然后,您可以对该新字段进行索引和查询。 - JohnnyHK

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接