如何加快 MongoDB 聚合查询速度?

问题描述 投票:0回答:1

requestedAtTimestamp_-1
上有一个索引,但查询经常需要超过 5 秒,Mongo Atlas 对我们大喊“
The ratio of documents scanned to returned exceeded 1000.0
”。

是否有其他索引可以帮助加快此聚合速度?

      {
        "$match": {
          "requestedAtTimestamp": {
            "$gte": 1730419200000
          }
        }
      },
      {
        "$sort": {
          "requestedAtTimestamp": 1
        }
      },
      {
        "$addFields": {
          "requestedAt": {
            "$toDate": "$requestedAtTimestamp"
          }
        }
      },
      {
        "$project": {
          "user": 1,
          "requestedAt": 1,
          "y": {
            "$year": "$requestedAt"
          },
          "m": {
            "$month": "$requestedAt"
          },
          "d": {
            "$dayOfMonth": "$requestedAt"
          },
          "h": {
            "$hour": "$requestedAt"
          },
          "x1": 1,
          "x2": 1
        }
      },
      {
        "$group": {
          "_id": {
            "user": "$user",
            "y": "$y",
            "m": "$m",
            "d": "$d",
            "h": "$h"
          },
          "x1": {
            "$sum": {
              "$cond": [
                "$x1",
                1,
                0
              ]
            }
          },
          "x2": {
            "$sum": {
              "$cond": [
                "$x2",
                1,
                0
              ]
            }
          },
          "total": {
            "$sum": 1
          }
        }
      }
Operation Execution Time   8.29 s
Examined:Returned Ratio    6,169.51
Keys Examined              623,121
Docs Returned              101
Docs Examined              623,121
Num Yields                 693
Response Length            16,593

  "planSummary": "IXSCAN { requestedAtTimestamp: -1 }",
  "planningTimeMicros": 1520,
  "cursorid": 7596442613226102117,
  "keysExamined": 623121,
  "docsExamined": 623121,
  "numYields": 693,
  "nreturned": 101,
  "queryHash": "08BAB40F",
  "planCacheKey": "AEE30401",
  "queryFramework": "classic",
  "reslen": 16593,
  "locks": {
    "FeatureCompatibilityVersion": {
      "acquireCount": {
        "r": 733
      }
    },
    "Global": {
      "acquireCount": {
        "r": 733
      }
    }
  },
  "readConcern": {
    "level": "local",
    "provenance": "implicitDefault"
  },
  "writeConcern": {
    "w": "majority",
    "wtimeout": 0,
    "provenance": "implicitDefault"
  },
  "storage": {
    "data": {
      "bytesRead": 2950570755,
      "timeReadingMicros": 3032242
    },
    "timeWaitingMicros": {
      "cache": 2352
    }
  },
  "cpuNanos": 5626765637,
  "remote": "3.238.26.30:53820",
  "protocol": "op_msg",
  "durationMillis": 8292,
  "v": "7.0.15",
  "isTruncated": false

文档格式

{
  "_id": {
    "$oid": "673601b421ea2eec217ce968"
  },
  "__v": {
    "$numberInt": "0"
  },
  "lastUpdatedTimestamp": {
    "$numberDouble": "1731592626011.0"
  },
  "requestedAtTimestamp": {
    "$numberDouble": "1731592624752.0"
  },
  "x1": "sadasdasdadasdasdasdasdasdasdasdasdasdasdasdsad",
  "x2": "0x7605ffddabf72cb9fa7ebba35c9c252d5cb4064cc04a314fe34ad2eea4a19c2bff90df2e1b6228ccd136a56b49c1d4e7dde039e94ccf676bbfa00e0ea216e681",
}
mongodb performance aggregation-framework
1个回答
0
投票

要检查此管道的性能,请考虑每个阶段正在做什么:

  1. 匹配
      {"$match": {
          "requestedAtTimestamp": {"$gte": 1730419200000}
      }},

根据计划总结,本阶段使用

{ requestedAtTimestamp: -1 }
上的索引。 由于排序阶段,它实际上会反向使用该索引,但这里完成的工作是相同的:

  • 在b树中找到
    1730419200000
  • 迭代索引中的其余值
  • 对于找到的每个值,获取相应的文档
  1. 排序
      {"$sort": {"requestedAtTimestamp": 1}},

这个阶段是完全没有必要的,因为管道中稍后会有一个分组阶段,并且即使输入已排序, $group 也不能保证输出文档的顺序。 但是,由于之前的 $match 使用了该字段上的索引,因此文档已按排序顺序获取,因此无论如何,此阶段实际上是无操作的。

  1. 添加字段
      {"$addFields": {"requestedAt": { "$toDate": "$requestedAtTimestamp"}}},

这会将包含双精度值的

requestedAtTimestamp
字段转换为 BSON 日期时间。 根据 BSON 规范,双精度值存储为

0x01 + field name + 64-bit double value

UTC 日期时间是

0x09 + field name + unsigned 64-bin integer millisecond since epoch

所以这里没有太大区别,只是 double 到 int 的转换。

  1. 项目
     {
        "$project": {
          "user": 1,
          "requestedAt": 1,
          "y": {"$year": "$requestedAt"},
          "m": {"$month": "$requestedAt"},
          "d": {"$dayOfMonth": "$requestedAt"},
          "h": {"$hour": "$requestedAt"},
          "x1": 1,
          "x2": 1
     }},

此阶段将删除除 _id、user、requestedAt、x1 和 x2 之外的所有原始字段。 这里最重要的是它将根据requestedAt 字段计算年、月、日和小时。 各一别。 由于该管道检查了 623,121 个文档,这意味着它将毫秒数转换为日期 4 次,总共进行了 2,494,484 次转换。 另请注意,如果不指定时区,这些转换将基于 UTC。

 {"$group": {
          "_id": {
             "user": "$user",
             "y": "$y",
             "m": "$m",
             "d": "$d",
             "h": "$h"
          },
          "x1": {"$sum": {"$cond": ["$x1",1,0]}},
          "x2": {"$sum": {"$cond": ["$x2",1,0]}},
          "total": {"$sum": 1}
  }}

此阶段按小时累积一些值。 这是唯一的阻塞阶段。 在该阶段处理完“所有”输入文档之前,无法返回第一个结果文档。 这里需要注意的是,使用非布尔字段值作为 $cond 条件可能会产生意想不到的结果,例如 "" 为 true。

整体

问题中提出的查询时间是 UTC 时间 11 月 1 日午夜。 如果在 11 月 14 日运行,则每个用户可能会返回 336 个文档。 该查询检查了 623,121 个文档,并且由于 $group 阶段,它必须在返回任何之前检查所有可能的输入文档。 这意味着返回的 101 个文档可能只是第一批,客户端将运行 getMore 来获取其余的文档。 后续的 getMore 请求不需要检查任何内容,因为在 $group 阶段之后结果集已经完成,它只需要返回该数据即可。

这里最大的改进是减少 $project 阶段完成的工作量。

一些想法:

使用 $dateToString 一次性投影年、月、日和小时,因此每个文档仅进行 1 次计算,而不是 4 次。也许
  • $dateToString: { date: {$toDate: "$requestedAtTimestamp"}, format: "%Y-%m-%dT%H", timezone: "UTC" }}
对于 $group 中的 _id 值,请使用以下内容,而不是在 $group 之前使用 $project:
  • _id:{ user: "$user", hour: {$divide:["$requestedAtTimestamp",3600000]} }
这会将日期划分为小时,而不需要计算年、月或日。  在 
after

分组中,使用项目将其转换为年、月、日和小时,这样这些计算就会完成数百次,而不是数十万次。

© www.soinside.com 2019 - 2024. All rights reserved.