DocumentDB聚合查询性能差异排查:生产环境查询极慢原因分析
问题描述
我们的DocumentDB集合包含70k条文档,生产环境中执行聚合查询耗时约2分48秒,但在另一性能更差的实例上,相同数据量下查询仅需6秒。我们怀疑是缓存问题,但执行db.collection.stats()未显示缓存相关信息,请问DocumentDB是否支持查询缓存?
生产环境慢查询executionStats
{ "queryPlanner": { "plannerVersion": 1.0, "namespace": "nnnnnnnn", "winningPlan": { "stage": "LIMIT_SKIP", "inputStage": { "stage": "SORT", "sortPattern": { "_tempSortEventId": -1.0 }, "inputStage": { "stage": "SUBSCAN", "inputStage": { "stage": "COLLSCAN" } } } } }, "executionStats": { "executionSuccess": true, "executionTimeMillis": "113311.697", "planningTimeMillis": "0.303", "executionStages": { "stage": "LIMIT_SKIP", "nReturned": "50", "executionTimeMillisEstimate": "113310.782", "inputStage": { "stage": "SORT", "nReturned": "50", "executionTimeMillisEstimate": "113310.776", "sortPattern": { "_tempSortEventId": -1.0 }, "inputStage": { "stage": "SUBSCAN", "nReturned": "70107", "executionTimeMillisEstimate": "110684.645", "inputStage": { "stage": "COLLSCAN", "nReturned": "70107", "executionTimeMillisEstimate": "67827.520", "inputStage": { "nReturned": "1", "executionTimeMillisEstimate": "0.048" } } } } } }, "serverInfo": { "host": "prod", "port": 27017.0, "version": "4.0.0" }, "ok": 1.0, "operationTime": Timestamp(1670838896,1) }
执行的聚合请求语句
aggregate( [ { "$match" : { "ApplicationId" : NUUID("dd25dadc-6b22-4f81-995b-2cce698a111a"), "FilterKeys" : { "$elemMatch" : { "Name" : "CorporateId", "Value" : "bbbfe3a7-fbec-4c88-8746-adf883a2ae6b" } } } }, { "$addFields" : { "_tempSortEventId" : { "$toLower" : "$EventId" } } }, { "$sort" : { "_tempSortEventId" : -1 } }, { "$project" : { "_tempSortEventId" : 0 } }, { "$skip" : 0 }, { "$limit" : 50 }])
解答
关于DocumentDB的查询缓存支持
DocumentDB确实支持查询缓存,默认会缓存最近使用的查询计划和数据,但db.collection.stats()不会直接展示缓存相关统计。你可以通过执行db.runCommand({getParameter: 1, "queryCacheSize": 1})查看缓存大小配置,通过db.runCommand({serverStatus: 1}).queryCache查看缓存的使用情况。
不过从你的执行计划和耗时数据来看,缓存不是导致性能差异的核心原因,真正的瓶颈在于以下两点:
全集合扫描(COLLSCAN)
执行计划显示查询触发了全表扫描,遍历了全部70107条文档,这说明没有针对$match条件创建有效索引。在性能较差的实例上查询更快,大概率是因为该实例的数据集已经完全加载到内存缓存中,避免了磁盘IO开销;而生产环境可能因为数据未缓存或有其他资源竞争,导致全表扫描耗时剧增。内存/磁盘排序开销
聚合中需要对所有匹配文档进行排序,执行计划的SORT阶段耗时占比极高。如果排序的数据量超过DocumentDB默认的100MB内存限制,会触发磁盘排序,这会进一步拉长查询时间。
优化建议
创建针对性索引:针对
$match条件创建复合索引,加速文档筛选:db.collection.createIndex({ "ApplicationId": 1, "FilterKeys.Name": 1, "FilterKeys.Value": 1 })该索引可以直接加速
$elemMatch对数组元素的匹配,避免全表扫描。优化排序逻辑:
- 考虑在文档中新增一个持久化字段(如
lowercaseEventId),存储转小写后的EventId,然后针对该字段创建索引,避免聚合时动态转换和内存排序。 - 如果无法修改文档结构,可以尝试创建包含排序字段的复合索引,让查询直接利用索引排序:
同时调整聚合逻辑,直接对db.collection.createIndex({ "ApplicationId": 1, "FilterKeys.Name": 1, "FilterKeys.Value": 1, "EventId": 1 })EventId转小写后排序(或在索引中预先处理),尽量让排序阶段利用索引。
- 考虑在文档中新增一个持久化字段(如
验证缓存状态:如果仍怀疑缓存问题,可以在生产环境执行查询后再次运行,观察耗时是否下降,以此验证是否命中缓存。但优先优化索引和查询逻辑,这才是解决性能问题的根本方案。
内容的提问来源于stack exchange,提问作者Roman Matkovskyi

