news 2026/9/13 1:21:40

MongoDB 聚合管道 Spilling 统计实战:慢查询日志字段、内存上限参数与 SBE golden 测试验证

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
MongoDB 聚合管道 Spilling 统计实战:慢查询日志字段、内存上限参数与 SBE golden 测试验证

MongoDB 聚合管道 Spilling 统计实战:慢查询日志字段、内存上限参数与 SBE golden 测试验证

【免费下载链接】mongoThe MongoDB Database项目地址: https://gitcode.com/GitHub_Trending/mo/mongo

本文围绕 MongoDB 仓库中一个 golden data 测试的期望输出文件 logs_spilling.md 展开:它固化了 14 类聚合管道在强制触发 Spilling(溢写)后,慢查询日志中应出现的 Spilling 统计字段与确定数值。读完本文,你将理解每个*Spills/*SpilledRecords/usedDisk字段的含义、控制各阶段溢写行为的internal*服务器参数、测试如何基于 profiling 日志提取并归一化这些统计,以及 golden 测试框架如何对该输出做 diff 与接受。

1. 这个文件在测试体系中的位置

logs_spilling.md 是 golden data 测试 logs_spilling_md.js 的"期望输出"(expected output)。MongoDB 的 golden data 测试框架将测试运行产生的文本输出与仓库中签入的已知正确输出逐字节比对,任何差异都会导致测试失败——框架设计与使用方式详见 golden_data_test_framework.md。该框架强调:输出必须确定、可 diff、增量变化,且期望输出文件不应手工修改,而是通过buildscripts/golden_test.pydiff/accept命令重新生成。

本测试验证的核心命题是:当聚合管道中的某个内存受限阶段(sort、group、hash lookup、$graphLookup、$bucketAuto、$setWindowFields 等)发生 Spilling 时,慢查询(Slow query)日志行中必须携带该阶段的 Spilling 统计属性。测试通过把各阶段内存上限压到 1 字节附近,强制每个阶段落盘,然后断言日志里出现了预期的统计键与确定数值。

文件位于expected_output/sbeFull/目录下。从目录命名与 golden 框架文档中internalQueryFrameworkControl的说明可以推断:sbeFull变体对应强制使用 SBE(Slot Based Execution)查询引擎运行的结果,同一测试在其他引擎/计划排序模式下会有各自独立的期望输出文件。

1.1 输出中的 "X" 占位约定

注意原文档中形如"sortSpilledBytes" : "X"的字符串。这不是模糊描述,而是测试代码 logs_spilling_md.js 中getSpillingAttrs()函数做的显式归一化:

  • SpillsSpilledRecords结尾的键(如sortSpillssortSpilledRecords)保留真实数值——它们是确定性的,直接参与 golden 比对;
  • SpilledBytesSpilledDataStorageSize结尾的键被替换为"X"——其取值依赖文档二进制大小与存储布局,跨平台不稳定;
  • spillStorage子对象中的各键(如timeWaitingMicros)同样替换为"X";其中data子键被整体过滤,测试中留有 TODO(SERVER-109672)说明待bytesRead字段确定性地出现后取消过滤。

因此,golden 比对真正约束的是:哪些统计键出现、每个阶段溢写次数与记录数、是否用了磁盘(usedDisk

2. 测试运行机制:从 setParameter 到慢查询日志

2.1 前置条件与标签

测试文件头部声明了三个 tags(logs_spilling_md.js):

  • requires_persistence:Spilling 目标是磁盘上的 Record Store,必须持久化环境可用;
  • requires_fcv_81:期望行为以 8.1 功能兼容性版本为准;
  • requires_profiling:整个测试依赖 profiling 日志。测试首行即执行db.setProfilingLevel(1, {slowms: -1})(L90),使所有命令都进入慢查询日志,从而保证每个aggregate都能被观察到;finally块中恢复原有 profiling 级别与所有被修改的服务器参数。

2.2 统计提取流程

每个用例的执行骨架(L47-L72):

  1. 以固定comment执行coll.aggregate(pipeline, {comment})
  2. db.adminCommand({getLog: "global"})取全局日志;
  3. 用 log.js 的findMatchingLogLine匹配msg: "Slow query"+command: "aggregate"+ 指定comment的日志行(comment 用于避免误抓子操作日志);
  4. 解析该 JSON 日志行的attr字段,按第 1.1 节规则抽取 Spilling 统计与 spill storage 统计;
  5. 以 Markdown 小节形式写出PipelineSlow query spilling stats,以及(如有)Slow query spill storage stats

2.3 各用例对应的内存上限参数

测试用setParameter把各阶段内存上限压到 1 字节附近触发溢写,并在finally中全部还原。参数定义见 stage_memory_limit_knobs.idl,均为运行时可设(set_at: [startup, runtime]):

用例(章节)服务器参数测试取值说明
1. Sort 大内存上限internalQueryMaxBlockingSortMemoryUsageBytes10003 条小文档({a:1..3})可放入 1KB,溢写
2. Sort 空集合同上(1000)无数据,溢写
3. Sort 溢写同上1每条记录都需落盘
4. 多次 Sort同上1两个$sort各自独立统计
6. GroupinternalDocumentSourceGroupMaxMemoryBytesinternalQuerySlotBasedExecutionHashAggApproxMemoryUseInBytesBeforeSpill1hash agg 溢写
7/8. TextOrinternalTextOrStageMaxMemoryBytes1仅在 feature flagExtendedAutoSpilling存在且开启时运行(L165)
9. BucketAutointernalDocumentSourceBucketAutoMaxMemoryBytes1$bucketAuto 分桶溢写
10. HashLookupinternalQuerySlotBasedExecutionHashLookupApproxMemoryUseInBytesBeforeSpill1$lookup 哈希构建侧溢写
11/12. GraphLookupinternalDocumentSourceGraphLookupMaxMemoryBytes1$graphLookup 游标侧溢写
13. HashLookupUnwindinternalQuerySlotBasedExecutionHashJoinApproxMemoryUseInBytesBeforeSpill1$lookup+$unwind 改写为 hash join 后溢写
14. SetWindowFieldsinternalDocumentSourceSetWindowFieldsMaxMemoryBytes1(SBE)/ 500(Classic)见下方特殊处理

其中internalQueryMaxBlockingSortMemoryUsageBytes的 IDL 定义(L216-L226)写明:它限制一条查询执行阻塞式排序的内存(字节),即使允许使用磁盘,该上限仍然约束内存消耗;默认值为100 * 1024 * 1024(100MB)。

SetWindowFields 的特殊处理(L396-L411):测试先对管道执行explain,若获胜计划中出现WINDOW阶段(即$setWindowFields被下推到 SBE),内存上限设为 1 字节;否则(Classic 引擎路径)设为 500 字节——因为 Classic 的DocumentSourceSetWindowFields在溢写后若仍装不下会直接报错,1 字节无法容忍。这解释了为何该 golden 文件放在sbeFull变体下却仍需引擎感知。

Timeseries 用例(L74-L88):创建timeField: "time"的时序集合,插入 50 条按bucketMaxSpanSeconds/10递增时间戳的文档,再执行$sort: {time: 1}。时序集合的时间顺序扫描使 sort 走有界(bounded)路径,但仍会记录 Spilling 统计。

3. 期望输出全文解读(14 个用例)

以下为 logs_spilling.md 中 14 个用例的完整期望内容,按主题分组讲解。

3.1 Sort 系列(用例 1–4)

用例 1:Sort with large memory limit—— 内存上限充足时不发生溢写,Spilling 统计为空对象{ }

[ { "$sort" : { "a" : 1 } } ]
{ }

用例 2:Sort with empty collection—— 空集合排序同样无溢写统计:

[ { "$sort" : { "a" : 1 } } ]
{ }

用例 3:Sort with spilling—— 1 字节上限下,3 条记录的阻塞式排序发生 5 次溢写、8 条记录落盘(记录数大于文档数是因为溢出文件合并阶段产生的中间记录),并置位usedDisk

[ { "$sort" : { "a" : 1 } } ]
{ "sortSpilledBytes" : "X", "sortSpilledDataStorageSize" : "X", "sortSpilledRecords" : 8, "sortSpills" : 5, "usedDisk" : true }

用例 4:Multiple sorts—— 管道内两个$sort(中间夹$limit: 3),统计是各 sort 阶段累积的结果:16 条记录、10 次溢写。这说明慢查询日志中的 Spilling 统计覆盖整条管道的同类阶段总量,而非单阶段:

[ { "$sort" : { "a" : 1 } }, { "$limit" : 3 }, { "$sort" : { "b" : 1 } } ]
{ "sortSpilledBytes" : "X", "sortSpilledDataStorageSize" : "X", "sortSpilledRecords" : 16, "sortSpills" : 10, "usedDisk" : true }

3.2 Group + Sort(用例 6)

hash 聚合阶段与后续 sort 阶段分别贡献一组统计键(group*sort*),这是多阶段 Spilling 各自独立计数的直接证据:

[ { "$group" : { "_id" : "$a", "b" : { "$sum" : "$b" } } }, { "$sort" : { "b" : 1 } } ]
{ "groupSpilledBytes" : "X", "groupSpilledDataStorageSize" : "X", "groupSpilledRecords" : 4, "groupSpills" : 4, "sortSpilledBytes" : "X", "sortSpilledDataStorageSize" : "X", "sortSpilledRecords" : 4, "sortSpills" : 3, "usedDisk" : true }

此外出现第三类小节Slow query spill storage stats

{ "timeWaitingMicros" : "X" }

从输出结构看,走 Record Store / spill storage 的聚合阶段(hash agg、hash join、window 等)还会上报spillStorage子统计(等待 Record Store 的时间等),而纯 sort 溢写路径不上报该子对象——用例 3、4 即无此小节。

3.3 TextOr(用例 7–8)

这两个用例仅在ExtendedAutoSpillingfeature flag 开启时存在,覆盖文本评分查询(textScore)相关的 TextOr 阶段:

用例 7:TextOr and projection$match+$text$addFieldstextScore):

[ { "$match" : { "$text" : { "$search" : "black tea" } } }, { "$addFields" : { "score" : { "$meta" : "textScore" } } } ]
{ "textOrSpilledBytes" : "X", "textOrSpilledDataStorageSize" : "X", "textOrSpilledRecords" : 4, "textOrSpills" : 4, "usedDisk" : true }

用例 8:TextOr and sort$sorttextScore排序)—— TextOr 与 sort 两组统计并存:

[ { "$match" : { "$text" : { "$search" : "black tea" } } }, { "$sort" : { "_" : { "$meta" : "textScore" } } } ]
{ "sortSpilledBytes" : "X", "sortSpilledDataStorageSize" : "X", "sortSpilledRecords" : 8, "sortSpills" : 5, "textOrSpilledBytes" : "X", "textOrSpilledDataStorageSize" : "X", "textOrSpilledRecords" : 4, "textOrSpills" : 4, "usedDisk" : true }

3.4 BucketAuto(用例 9)

$bucketAuto将输入自动分桶,分桶过程受internalDocumentSourceBucketAutoMaxMemoryBytes约束:

[ { "$bucketAuto" : { "groupBy" : "$a", "buckets" : 2, "output" : { "sum" : { "$sum" : "$b" } } } } ]
{ "bucketAutoSpilledBytes" : "X", "bucketAutoSpilledDataStorageSize" : "X", "bucketAutoSpilledRecords" : 13, "bucketAutoSpills" : 7, "usedDisk" : true }

3.5 HashLookup(用例 10)

people集合(4 条:Alex、Drew、Justin、Parker)执行$lookup关联students(8 条,其中 Alex、Harley 各有重名),构建侧哈希表被限制到 1 字节内存:

[ { "$lookup" : { "from" : "logs_spilling_md_students", "localField" : "name", "foreignField" : "name", "as" : "matched" } } ]
{ "hashLookupSpilledBytes" : "X", "hashLookupSpilledDataStorageSize" : "X", "hashLookupSpilledRecords" : 14, "hashLookupSpills" : 16, "usedDisk" : true }
{ "timeWaitingMicros" : "X" }

注意from中的集合名被渲染为测试名拼接的logs_spilling_md_students(测试以db[jsTestName() + "_students"]建集合,L228-L230),这正是 golden 测试"输出必须跨运行确定"要求的体现——集合名必须可预测。

3.6 GraphLookup(用例 11–12)

在 7 条构成有向图的文档上执行$graphLookupstartWith: 1,沿to->_id递归):

用例 11:Graph lookup

[ { "$limit" : 1 }, { "$graphLookup" : { "from" : "coll", "startWith" : 1, "connectFromField" : "to", "connectToField" : "_id", "as" : "path", "depthField" : "depth" } } ]
{ "graphLookupSpilledBytes" : "X", "graphLookupSpilledDataStorageSize" : "X", "graphLookupSpilledRecords" : 2, "graphLookupSpills" : 2, "usedDisk" : true }

用例 12:Graph lookup with unwind and sort—— 追加$unwind: "$path"$sort: {"path.depth": 1}后,期望统计与用例 11 完全一致(2 记录 / 2 次溢写),说明该场景下后续 unwind/sort 未产生额外 Spilling 统计。两个用例均带 spill storage 小节:

{ "timeWaitingMicros" : "X" }

3.7 HashLookupUnwind(用例 13)

$lookup + $unwind会被优化为 hash join(受internalQuerySlotBasedExecutionHashJoinApproxMemoryUseInBytesBeforeSpill控制)。测试还额外创建了两个 dummy 索引({"dummy": -1, "locationName": -1}{"dummy": 1, "name": -1}),注释说明这是为 join 优化提供 multikey 信息的(L350-L352):

[ { "$lookup" : { "from" : "logs_spilling_md_locations", "localField" : "locationName", "foreignField" : "name", "as" : "location" } }, { "$unwind" : "$location" }, { "$project" : { "locationName" : false, "location.extra" : false, "location.coordinates" : false, "colors" : false } } ]
{ "hashLookupSpilledBytes" : "X", "hashLookupSpilledDataStorageSize" : "X", "hashLookupSpilledRecords" : 6, "hashLookupSpills" : 6, "usedDisk" : true }
{ "timeWaitingMicros" : "X" }

值得注意:hash join 路径复用了hashLookup*前缀的统计键。

3.8 SetWindowFields(用例 14)

sbeFull变体下窗口阶段被下推到 SBE(WINDOW阶段),1 字节上限即可触发溢写。$limit变体

[ { "$setWindowFields" : { "partitionBy" : "$a", "sortBy" : { "b" : 1 }, "output" : { "sum" : { "$sum" : "$b" } } } } ]
{ "setWindowFieldsSpilledBytes" : "X", "setWindowFieldsSpilledDataStorageSize" : "X", "setWindowFieldsSpilledRecords" : 4, "setWindowFieldsSpills" : 4, "sortSpilledBytes" : "X", "sortSpilledDataStorageSize" : "X", "sortSpilledRecords" : 13, "sortSpills" : 7, "usedDisk" : true }

追加$limit: 1变体——窗口阶段自身统计从 4/4 降为 3/3,而sort*统计不变,表明$limit削减了窗口处理的输入量,但内部排序的溢写总量不受尾部截断影响:

[ { "$setWindowFields" : { "partitionBy" : "$a", "sortBy" : { "b" : 1 }, "output" : { "sum" : { "$sum" : "$b" } } } }, { "$limit" : 1 } ]
{ "setWindowFieldsSpilledBytes" : "X", "setWindowFieldsSpilledDataStorageSize" : "X", "setWindowFieldsSpilledRecords" : 3, "setWindowFieldsSpills" : 3, "sortSpilledBytes" : "X", "sortSpilledDataStorageSize" : "X", "sortSpilledRecords" : 13, "sortSpills" : 7, "usedDisk" : true }

两个变体同样附带{ "timeWaitingMicros" : "X" }的 spill storage 小节。

4. 源码纵深:统计从哪里来

4.1 SpillingStats 四类计数器

所有 Spilling 统计都由 spilling_stats.h 中的SpillingStats类累积,共四个计数器(L73-L82):

  • _spills:发生溢写的次数(对应日志*Spills);
  • _spilledBytes:因溢写而释放的内存字节数(对应*SpilledBytes,被测试归一化为 "X");
  • _spilledDataStorageSize:溢写占用的磁盘空间字节数(对应*SpilledDataStorageSize,取累计最大值而非求和,见updateSpilledDataStorageSizestd::max语义);
  • _spilledRecords:写入 Record Store 的记录条数(对应*SpilledRecords)。

这也解释了测试归一化策略的合理性:次数与记录数由数据量决定、可复现;字节数与磁盘占用受文档序列化尺寸影响、跨平台不稳定。

4.2 慢查询日志中的承载结构

op_debug.h 中PlanSummaryStats持有按阶段分组的统计映射absl::flat_hash_map<PlanSummaryStats::SpillingStage, SpillingStats> spillingStatsPerStage,即每条慢查询日志的attr里每个*Spills/*SpilledRecords/*SpilledBytes/*SpilledDataStorageSize键都对应一种SpillingStage枚举值(sort、group、bucketAuto、hashLookup、graphLookup、setWindowFields、textOr 等前缀即由此展开)。usedDisk则是PlanSummaryStats的独立布尔字段(L190),表示该查询计划整体使用了磁盘。

从源码结构看,SBE 侧各阶段实现各自向该体系写入统计:排序(sort.cpp、sort_stage_sort_impl.cpp)、哈希聚合(hash_agg.cpp、block_hashagg.cpp)、哈希查找(hash_lookup.cpp)、哈希连接(hash_join.cpp、hash_lookup_unwind.cpp)、窗口函数(window.cpp);Classic 路径则由 sort_stage.cpp、group_base_stage.cpp、graph_lookup_stage.cpp、internal_set_window_fields_stage.cpp 等实现同样的上报。golden 文件放在sbeFull变体下,比对的就是 SBE 路径产出的键集合与数值。

4.3 内存上限参数的定义位置

测试调用的九个internal*内存旋钮统一定义在 stage_memory_limit_knobs.idl(如internalDocumentSourceGroupMaxMemoryBytes位于 L51 附近,internalQueryMaxBlockingSortMemoryUsageBytes位于 L216-L226)。以阻塞式 sort 为例,其 IDL 描述明确:"限制一条查询执行阻塞式排序愿意使用的内存(字节);若允许磁盘使用,可能排序更多数据,但该限制仍约束内存消耗",默认 100MB。生产环境中该参数用于给单条查询的排序内存划红线;测试则把它压到 1 字节来穷举溢写路径。

5. 实操:运行、比对与接受新输出

5.1 运行该 golden 测试

该测试属于query_golden套件,标准运行方式参照同套件 README.plan_stability.md 中给出的 resmoke 用法,将引擎控制参数指向 SBE 以匹配sbeFull期望输出:

buildscripts/resmoke.py run \ --suites=query_golden_classic \ '--mongodSetParameters={internalQueryFrameworkControl: forceSBEEngine}' \ jstests/query_golden/logs_spilling_md.js

其中internalQueryFrameworkControl正是 golden 框架文档中说明的、决定查询框架(Classic 或 SBE)的开关,不同取值对应expected_output/下不同子目录的期望文件。注意运行环境必须满足测试标签:持久化存储(Spilling 落盘)与 profiling 支持;requires_fcv_81意味着期望输出以 8.1 FCV 行为为准。

5.2 diff 与 accept 工作流

按 golden_data_test_framework.md 的流程,一次性完成工作站配置(buildscripts/golden_test.py setup,设置GOLDEN_TEST_CONFIG_PATH)后:

# 查看本次运行与期望输出的差异 buildscripts/golden_test.py diff # 将实际输出批量接受为新的期望输出(不要手工编辑期望文件) buildscripts/golden_test.py accept

若同一测试需要在多种构建变体/引擎下同步刷新期望文件,使用框架提供的批量命令:

buildscripts/golden_test.py --verbose clean-run-accept jstests/query_golden/logs_spilling_md.js

该命令通过resmoke.py find-suites判定测试所属套件,并在必要时以不同的internalQueryFrameworkControl取值多轮运行,保证各变体的期望输出一致更新。

5.3 失败时如何定位

  • 若 diff 中出现新增/缺失的统计键(如某阶段突然出现*Spills),通常是该阶段引入了新的 Spilling 路径或统计上报,需要对照 4.2 节中对应的阶段实现文件确认上报逻辑;
  • *SpilledRecords/*Spills数值变化(如用例 14 的 4/4 变为 3/3),说明溢写合并策略或 Record Store 批处理行为改变了,应检查相应 SBE 阶段(sort/hash_agg/window 等)的溢写实现;
  • usedDisktruefalse则意味着溢写未再发生,往往与内存上限参数或阶段改写(例如 join 优化是否生效)相关。

6. 小结

logs_spilling.md 这份看似简单的期望输出文件,实际上固化了 MongoDB 慢查询日志中 Spilling 可观测性的完整契约:14 个用例覆盖阻塞式排序、时序集合排序、hash 聚合、TextOr、$bucketAuto$lookup(hash lookup 与 hash join 两条路径)、$graphLookup$setWindowFields共 8 类内存受限阶段;每个用例断言了确定性的溢写次数与记录数、usedDisk标志,以及(对走 Record Store 的阶段)spillStorage.timeWaitingMicros的存在性。配合 logs_spilling_md.js 中"压低内存上限 → 强制溢写 → 从 profiling 日志抽取 → 归一化字节量 → golden 比对"的闭环,以及 spilling_stats.h、op_debug.h 与 stage_memory_limit_knobs.idl 中的源码实现,它既是一份运维上解读*Spills/*SpilledRecords/usedDisk日志字段的权威参照,也是一套防止 Spilling 统计回归的工程防线。

【免费下载链接】mongoThe MongoDB Database项目地址: https://gitcode.com/GitHub_Trending/mo/mongo

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/13 1:20:35

Python函数与模块化编程核心概念与实践

1. 函数与模块的基本概念在编程世界中&#xff0c;函数和模块是构建复杂系统的两大基石。它们就像建筑中的砖块和预制构件&#xff0c;让开发者能够以更高效、更有序的方式组织代码。1.1 函数的本质与价值函数是一段可重复使用的代码块&#xff0c;它接受输入参数&#xff0c;执…

作者头像 李华
网站建设 2026/9/13 1:18:31

Unity打造Android桌面宠物:从悬浮窗嵌入到交互实现全解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/13 1:16:02

国自然申请改革:如何在有限篇幅内展现研究深度

1. 国自然申请改革背景与核心挑战2026年国家自然科学基金申请迎来重大变革&#xff0c;最显著的变化是申请材料篇幅的大幅压缩。这一改革直接打破了延续多年的"以量取胜"评审模式&#xff0c;对科研人员的学术表达能力提出了更高要求。在有限的篇幅内既要做到简洁清晰…

作者头像 李华
网站建设 2026/9/13 1:14:49

Frank-Wolfe算法MATLAB实现:大规模约束优化的高效解法

简介&#xff1a;Frank-Wolfe算法是一种经典的约束凸优化方法&#xff0c;由J. Frank和D. Wolfe于1956年提出&#xff0c;在处理大型稀疏数据集时尤为高效&#xff0c;适合需要求解带约束目标函数最小化问题的场景。这份Matlab实现资源&#xff0c;面向正在学习优化算法原理、需…

作者头像 李华
网站建设 2026/9/13 1:08:30

ASP档案管理系统开发全攻略:环境配置、模块改造与答辩部署

简介&#xff1a;一份面向计算机专业毕业设计的ASP档案管理系统完整项目包&#xff0c;适合需要完成Web开发课题的本专科学生&#xff0c;也可作为企业文档管理开发的基础参考。系统基于ASP与Access数据库实现&#xff0c;覆盖用户登录与权限管理、档案上传下载、关键词检索、分…

作者头像 李华