【问题标题】: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 数组条目级别,您可能需要考虑重构集合以将数组条目存储为单独的文档,例如thisxs 级别的索引和查询会简单得多。

标签: mongodb aggregation-framework


【解决方案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}

【讨论】:

    猜你喜欢
    • 2020-07-26
    • 2021-02-13
    • 1970-01-01
    • 2022-01-10
    • 1970-01-01
    • 1970-01-01
    • 2015-05-06
    • 2017-09-13
    • 2018-01-30
    相关资源
    最近更新 更多