慢聚合:按照过滤+嵌套对象排序文档

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

跟我学Elasticsearch(7) es的嵌套聚合,下钻分析,聚合分析

在mongodb聚合中将多个对象合并为一个对象

ES聚合过滤与排序

〈三〉ElasticSearch的认识:搜索过滤排序

嵌套模式/子文档对象中的猫鼬 findById() - 聚合