ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

MongoDB 查询计划黄金测试:sbeFull 变体下 $or 谓词下推场景的预期输出解析

MongoDB 查询计划黄金测试:sbeFull 变体下 $or 谓词下推场景的预期输出解析 MongoDB 查询计划黄金测试sbeFull 变体下 $or 谓词下推场景的预期输出解析【免费下载链接】mongoThe MongoDB Database项目地址: https://gitcode.com/GitHub_Trending/mo/mongo本文以 MongoDB 源码仓库中的黄金测试Golden Data Test预期输出文件 match_or_predicate_pushdown.md 为核心样本讲解sbeFull执行引擎变体下一个带嵌套$or/$and谓词的聚合查询是如何被优化器枚举、选出获胜计划winning plan并固化为可回归比对的金标准的同时结合其对应的测试脚本 match_or_predicate_pushdown_md.js 与黄金测试框架文档说明该场景所复现的规划器振荡缺陷SERVER-106983、预期输出各小节的含义以及如何用仓库自带工具对这类输出做 diff 与批量接受。1. 这个文件在仓库中的定位查询黄金测试的变体预期输出jstests/query_golden/是 MongoDB 的查询优化黄金测试套件。它的机制是每个*_md.js测试脚本在 mongod 上执行查询/聚合管道把输入数据、管道 输出结果集、explain 摘要、索引列表序列化成 Markdown 文本然后与expected_output/变体名/目录下签入的.md文件逐字节比对任何差异都会导致测试失败。这一机制的通用规范见 golden_data_test_framework.md核心原则包括输出必须确定且可重复跨平台一致因此框架对 explain 做了摘要化处理剥除不稳定字段期望输出文件禁止手工修改只能通过重新运行测试后拷贝生成物来更新测试应同时打印输入与输出便于评审者直接核对期望值是否正确。之所以按目录切分多个变体sbeFull、sbeRestricted、sbeDisabled、featureFlagSbeFull、internalEnableJoinOptimization等是因为同一查询在不同执行引擎/功能开关组合下可能产生不同的计划形态。sbeFull目录对应 SBEStage-Based Executor基于代码生成的聚合/查询执行引擎完全启用的构建变体。本文分析的正是其中一个文件的完整预期内容。测试脚本本身通过 Bazel 规则聚合进套件jstests/query_golden/BUILD.bazel 中all_javascript_files通过glob([*.js])收集本目录全部测试脚本。2. 测试场景设计复现规划器 bounds 振荡缺陷预期输出由测试脚本 match_or_predicate_pushdown_md.js 生成脚本头部注释交代了它的出身/** * Test a particular nested $and/$or query. This test was designed to reproduce SERVER-106983, a bug * in which the plan enumerator only generates one plan for this query, but the plan oscillates * between two sets of bounds. */即这是一个针对嵌套$and/$or谓词的特定查询的复现用例规划器枚举器plan enumerator对此查询只生成一份计划但该计划的 index bounds 会在两组取值间振荡。把它固化成黄金输出后任何导致 bounds 漂移或计划树变化的改动都会被 diff 出来。2.1 数据与索引脚本使用db.or_pred_pushdown_coll集合插入两条文档见预期输出开头段落const docs [ { _id: 187, t: ISODate(1970-01-01T00:00:00Z), m: {m1: NumberInt(0), m2: NumberInt(0)}, array: [], // This makes the index we create below multikey. a: NumberInt(0), b: NumberInt(0), }, { _id: 83, t: ISODate(1970-01-01T00:00:00Z), m: {m1: NumberInt(0), m2: ISODate(1970-01-01T00:00:00Z)}, array: , a: NumberInt(0), b: NumberInt(0), }, ];其中_id: 187文档的array: []是刻意设计的——注释写明它使复合索引成为multikey 索引。随后通过resetCollection实现于 jstests/query_golden/libs/utils.js完成 drop → 插入 → 打印文档 → 建索引的完整复位resetCollection(coll, docs, /* indexes */ [{t: 1, array: 1}]);预期输出中对应段落为[jsTest] Resetting collection. Inserting docs: ... Collection count: 2 [jsTest] Creating indexes: ... { array : 1, t : 1 }resetCollection的完整行为是先coll.drop()对未显式指定_id的文档补顺序_idsequentialIds逐条打印文档后批量插入打印集合计数再按给定顺序创建全部索引。这一打印输入的动作正是黄金测试框架输入输出并排呈现最佳实践的具体落地。2.2 查询管道被测的管道是一个单一$match阶段同时携带$or与$and两个逻辑分组const pipeline [ { $match: { $or: [{t: {$exists: true}}, {_id: 0, a: 0}], $and: [{array: {$nin: [0]}}, {array: {$eq: }}], }, }, ]; outputAggregationPlanAndResults(coll, pipeline);各谓词的匹配语义决定了 2.3 节为什么只返回一条结果$or: [ {t: {$exists: true}}, {_id: 0, a: 0} ]文档满足t字段存在或_id 0且a 0之一。两条文档均有t故该分支全部通过第二条分支则精确指向_id: 0的文档实际不存在它被保留是为了让规划器枚举出_id_索引扫描这一分支计划。$and: [ {array: {$nin: [0]}}, {array: {$eq: }} ]array数组中不含0且array字段值本身等于空字符串。_id: 187文档的array: []虽满足$nin空数组不匹配任何元素但不满足$eq: _id: 83文档的array: 同时满足两者。3. 预期输出逐段解析以下各小节标题### Pipeline/### Results/### Total indexes on the collection/### Summarized explain均由公共工具函数 outputAggregationPlanAndResults 生成它执行coll.aggregate(pipeline)拿结果、coll.explain().aggregate(pipeline)拿 explain再用formatExplainRoot把 explain 摘要化、用tojsonMultiLineSortKeys做键排序序列化以保证输出稳定。下面对照 预期输出文件 逐段说明。3.1 Pipeline 小节该小节把输入的管道原样序列化进输出键排序后的 JSON保证评审者能把输入与后面的计划并排核对[ { $match : { $or : [ { t : { $exists : true } }, { _id : 0, a : 0 } ], $and : [ { array : { $nin : [ 0 ] } }, { array : { $eq : } } ] } } ]3.2 Results 小节结果集只有一条——_id: 83的文档与 2.2 节的语义分析一致{ _id : 83, a : 0, array : , b : 0, m : { m1 : 0, m2 : ISODate(1970-01-01T00:00:00Z) }, t : ISODate(1970-01-01T00:00:00Z) }3.3 Total indexes on the collection 小节该小节由outputAvailableIndexes生成列出集合上的全部索引名[ _id_, t_1_array_1 ]即除默认_id_索引外只存在测试显式创建的复合索引t_1_array_1即{t: 1, array: 1}。3.4 Summarized explain 小节获胜计划与 bounds小节开头的Execution Engine: sbe行由getEngine(explain)提取确认该变体下查询以 SBE 引擎执行。核心是queryPlanner摘要queryShapeHash稳定标识查询形状用于计划缓存rejectedPlans为空winningPlan为如下阶段树{ queryShapeHash : 22F20D87ABD4D959DDE7E39FDB212E091657B9BF7FA2D6EB366103791557D83C, rejectedPlans : [ ], winningPlan : [ { filter : { $and : [ { array : { $not : { $eq : 0 } } }, { array : { $eq : } } ] }, nss : test.or_pred_pushdown_coll, stage : FETCH }, { stage : OR }, { filter : { t : { $exists : true } }, nss : test.or_pred_pushdown_coll, stage : FETCH }, { direction : forward, indexBounds : { array : [ [\\, \\] ], t : [ [MinKey, MaxKey] ] }, indexName : t_1_array_1, isMultiKey : true, isPartial : false, isSparse : false, isUnique : false, keyPattern : { array : 1, t : 1 }, multiKeyPaths : { array : [ array ], t : [ ] }, nss : test.or_pred_pushdown_coll, stage : IXSCAN }, { filter : { a : { $eq : 0 } }, nss : test.or_pred_pushdown_coll, stage : FETCH }, { direction : forward, indexBounds : { _id : [ [0.0, 0.0] ] }, indexName : _id_, isMultiKey : false, isPartial : false, isSparse : false, isUnique : true, keyPattern : { _id : 1 }, nss : test.or_pred_pushdown_coll, stage : IXSCAN } ] }从源码结构看这棵树体现了典型的OR 计划分解 谓词下推predicate pushdown形态可以分三层理解索引扫描层两个 IXSCAN 分别对应$or的两个分支——t_1_array_1复合索引扫描isMultiKey: truemultiKeyPaths显示只有array是 multikey 路径呼应 2.1 节array: []的设计。bounds 为t: [MinKey, MaxKey]、array: [, ]即t分支没有可用的定值条件仅$exists而array的等值边界[, ]是从$and组中下推到索引扫描的等值谓词。_id_唯一索引扫描bounds 为[0.0, 0.0]对应$or第二分支的_id: 0点查。FETCH 过滤层每个 IXSCAN 之上各挂一个 FETCH携带无法进入索引 bounds 的残留过滤条件——复合索引分支上的 FETCH 保留t: {$exists: true}_id_分支上的 FETCH 保留a: {$eq: 0}最外层 FETCH 再应用$and的完整过滤。顶层 FETCH 中的$nin重写值得注意的是输入管道写的是array: {$nin: [0]}而获胜计划顶层 FETCH 的 filter 显示为$not: {$eq: 0}。这是优化器对单元素$nin的等价改写$nin: [v]≡NOT ($eq: v)也是该黄金输出锁定的可观测行为之一如果 SBE 全量启用下的谓词重写规则发生变化这一行会直接出现在 diff 里。中间的{stage: OR}节点把两个分支的扫描结果做并集合并——这正是 or predicate pushdown$or谓词下推测试名的由来规划器把$or分解为多个独立的索引扫描分支各自携带下推的 bounds再由 OR 阶段归并。而 SERVER-106983 的缺陷正是枚举器为这种分解只产出一个计划但该计划的 bounds 会在两组取值间振荡例如array边界在[, ]与全范围之间漂移。把整个winningPlan结构含 bounds、multikey 标记、各 FETCH 的 filter全部签入金标准就是对振荡这一非确定性行为最敏感的回归探针——任何 bounds 或阶段顺序变化都会使 diff 非空。4. 如何验证、diff 与更新这类黄金输出框架层面golden_data_test_framework.md 给出了标准工作流首次使用需一次性初始化本地配置buildscripts/golden_test.py setup或手工创建~/.golden_test_config.yml并导出GOLDEN_TEST_CONFIG_PATH配置项包括outputRootPattern每次运行写 expected/actual 输出的根路径模式与diffCmd默认git diff --no-index {{expected}} {{actual}}运行测试后若实际输出与签入文件不一致测试失败并各自落盘actual/与expected/两份产物用 buildscripts/golden_test.py 管理输出# 列出最近一次测试运行的全部输出 buildscripts/golden_test.py list # diff 最近一次运行中所有不一致的输出 buildscripts/golden_test.py diff # 将最近一次运行的实际输出批量拷贝为新的期望输出 buildscripts/golden_test.py accept对于像本文场景这样会跑多个 passthrough/构建变体因此存在sbeFull、sbeDisabled等多个期望文件的测试文档专门提供了批量更新方式buildscripts/golden_test.py --verbose clean-run-accept jstests/query_golden/NAME_OF_TEST.js该命令通过resmoke.py find-suites判定测试所属的 passthrough 套件并逐一运行若测试仅属于query_golden_classicpassthrough则会假定其需要在多种internalQueryFrameworkControl取值下运行对应不同执行引擎变体从而一次更新全部变体目录下的期望文件。这正解释了expected_output/下按变体分目录的组织方式与本文文件所在目录sbeFull/的来源。5. 小结match_or_predicate_pushdown.md 是 SBE 完全启用变体下、嵌套$or/$and查询的黄金输出它固化了输入文档、复合索引{t: 1, array: 1}、管道、单条匹配结果以及双 IXSCAN OR 分层 FETCH的完整获胜计划树该计划树是$or谓词下推的直接证据_id分支点查 bounds[0.0, 0.0]、复合索引分支array: [, ]等值边界 t: [MinKey, MaxKey]全范围顶层 FETCH 同时锁定了单元素$nin被改写为$not {$eq}的可观测行为作为 SERVER-106983 的复现用例它把规划器 bounds 振荡这一难以用断言精确表述的缺陷转化成了对winningPlan全结构的确定性比对相关工具链resetCollection、outputAggregationPlanAndResults、golden_test.py均位于 jstests/query_golden/libs/utils.js、jstests/libs/query/golden_test_utils.js 与 buildscripts/golden_test.py配合 docs/golden_data_test_framework.md 的 diff/accept 流程即可完成该输出的验证与更新。【免费下载链接】mongoThe MongoDB Database项目地址: https://gitcode.com/GitHub_Trending/mo/mongo创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表