慢聚合:按照过滤+嵌套对象排序文档
Posted
技术标签:
【中文标题】慢聚合:按照过滤+嵌套对象排序文档【英文标题】:Slow aggregation: sorting documents according to filtered + nested objects 【发布时间】:2022-01-23 02:04:06 【问题描述】:我的文档如下所示:
docs = [
'id': 1,
'xs': [
'name': 'foo', 'value': 0.5,
'name': 'bar', 'value': 0.3,
],
,
'id': 2,
'xs': [
'name': 'foo', 'value': 0.9,
'name': 'bar', 'value': 0.1,
],
,
]
我想根据 xs.value
分别获取每个 xs.name
值的前 N 个文档(降序排序 + 限制)。
我尝试使用$unwind
和$sort
来执行此操作,但感觉有点慢。我有大约 6000 个文档,每个文档在 xs
中有 20 个元素,以下聚合大约需要 3 分钟:
steps = [
'$match': query,
'$unwind': '$xs',
'$match': 'xs.name': "foo",
'$sort': 'xs.value': -1,
'$limit': 10
]
关于如何加快速度的任何想法?我想我可以通过多种方式编写聚合,但我不确定哪种方式具有最大的加速潜力。
谢谢!
编辑: 以下步骤:
'$match': **query, 'xs.name': "foo",
'$unwind': '$xs',
'$match': 'xs.name': "foo",
'$limit': 1,
大约需要一分钟才能完成,甚至不需要排序
指数如下:
'xs.name': 'v': 2, 'key': [('xs.name', 1)],
'xs.value-1': 'v': 2, 'key': [('xs.value', -1)]
编辑 2: 另一个尝试:
'$match': query,
'$project':
'items':
'$filter': 'input': '$xs', 'as': 'this', 'cond': '$eq': ['$$this.name', "foo"]
,
,
'$limit': 1,
非常快,但是添加这个:
'$sort': 'xs.value': -1,
在$limit
之前让它变得很慢。
【问题讨论】:
您可能希望将您的'$match': 'xs.name': "foo"
放入第一个$match
以进行更有选择性的查询。另一件事是,您能否提供您当前的索引,以便我们了解可以提供哪些帮助?
你是对的,但在我的用例中,所有对象的每个值都为xs.name
。我不将这些用作直接文档字段的原因是因为它们可能经常更改/以各种方式查询。我在xs.name
上有一个升序索引,但我认为我在xs.value
上缺少一个索引。
@eloaf,正如@ray 指出的那样,我认为您的$match
条件是瓶颈。据我所知,频繁的文档更改不会成为问题,您必须在匹配条件中添加xs.name
,正如指出的那样。您能否提供query
变量的值,以便我们可以相应地建议索引
谢谢我用一些额外的信息编辑了我的原始问题
我认为$unwind
可能会使情况变得更加复杂。如果查询最里面的xs
数组条目级别,您可能需要考虑重构集合以将数组条目存储为单独的文档,例如this。 xs
级别的索引和查询会简单得多。
【参考方案1】:
在没有 $sort 的情况下它工作得非常快,因为没有阻塞阶段,这意味着光标在 pipline 处理第一批后立即获得结果,而使用 $limit 它不需要处理其余的文档。
$sort 和 $group 等阻塞阶段需要上一个阶段处理所有文档,然后管道才能继续。
https://docs.mongodb.com/manual/core/aggregation-pipeline/#pipeline-operators-and-indexes 中关于如何在聚合中使用索引的一句话:
以下管道阶段可以利用索引:
$match
如果 $match 阶段发生在管道的开头,它可以使用索引来过滤文档。
$排序
$sort 阶段可以使用索引,只要它前面没有 $project、$unwind 或 $group 阶段。
这意味着在 $unwind 之后不使用“xs.name”索引,并且对“xs.value”进行内存排序,这使得管道更慢。
恐怕从索引中受益的唯一方法是更改文档的结构 - 将集合拆分为 2,从“docs”中删除“xs”数组并将子文档保存在单独的 doc_xs 集合中:
docs = [
'id': 1
,
'id': 2
,
]
doc_xs = [
'name': 'foo', 'value': 0.5, 'doc_id':1,
'name': 'bar', 'value': 0.3, 'doc_id':1,
'name': 'foo', 'value': 0.9, 'doc_id':2,
'name': 'bar', 'value': 0.1, 'doc_id':2
]
聚合将是:
doc_xs.aggregate([
$match: "name": "foo",
$sort: "value": -1,
$limit: 10,
$lookup:
from: "docs",
localField: "doc_id",
foreignField: "id",
as: "doc"
])
它可以受益于 doc_xs 集合上的复合索引 "name":1, "value":-1
和 docs 上的 "id": 1
【讨论】:
以上是关于慢聚合:按照过滤+嵌套对象排序文档的主要内容,如果未能解决你的问题,请参考以下文章
Spring数据mongodb过滤字符串集合中的嵌套对象ID