MongoDB 分片过滤场景下 DISTINCT_SCAN 计划缓存行为深度解析:基于 shard_filtering_plan_cache Golden Test

发布时间:2026/9/14 12:29:34

MongoDB 分片过滤场景下 DISTINCT_SCAN 计划缓存行为深度解析:基于 shard_filtering_plan_cache Golden Test MongoDB 分片过滤场景下 DISTINCT_SCAN 计划缓存行为深度解析基于 shard_filtering_plan_cache Golden Test【免费下载链接】mongoThe MongoDB Database项目地址: https://gitcode.com/GitHub_Trending/mo/mongo分片集群中每个分片可能持有多个孤儿 chunkorphaned chunk此时基于分片键的 DISTINCT_SCAN 计划会带上一项关键属性isShardFiltering: true。本文以 MongoDB 仓库中 golden test 的预期输出 shard_filtering_plan_cache.md 为主线结合其驱动测试脚本与工具库逐字段拆解计划缓存条目的结构、inactive/active 状态流转机制并给出完整的复现与运行方式。读完本文你将能看懂 MongoDB 计划缓存输出中每一层的含义并掌握如何在分片集群中验证 DISTINCT_SCAN 与分片过滤的缓存行为。一、背景DISTINCT_SCAN、分片过滤与孤儿文档在 MongoDB 分片集群中数据按分片键shard key切分成多个 chunk 分布在各分片上。正常情况下每个 chunk 只属于一个分片但迁移moveChunk过程中可能残留不属于当前分片的孤儿文档orphaned documents。查询执行时必须对扫描结果做分片过滤shard filtering只返回当前分片实际拥有非孤儿的文档。DISTINCT_SCAN是查询规划器的一种特殊索引扫描当索引前缀能保证键值有序且唯一时它可以跳过重复键值只扫描每个不同键值的第一个条目从而高效支持去重、$group、$top/$bottom、$first/$last等操作。当分片上的索引扫描同时承担孤儿文档过滤职责时计划中对应阶段会带有isShardFiltering : true标记。本 golden test 的核心目标见 测试文件注释正是验证在分片持有多个孤儿 chunk 的分片集合上DISTINCT_SCAN 计划如何被存入计划缓存plan cache并在两次执行间完成从 inactive 到 active 的状态转换。测试还挂有featureFlagShardFilteringDistinctScan与featureFlagGetExecutorDeferredEngineChoice特性开关并声明requires_fcv_82说明该行为至少要求 FCV 8.2 且两个特性开关开启。二、测试环境双分片 六 chunk 双向孤儿数据测试环境的搭建逻辑集中在 golden_sharding_utils.js 的setupShardedCollectionWithOrphans()函数中具体过程如下启动分片集群new ShardingTest({shards: 2})共两个分片 rs0、rs1外加 mongos。启用分片通过enableSharding指定主分片为 shard0。构造数据分布shard0 持有 chunkchunk1_s0、chunk2_s0、chunk3_s0shard1 持有 chunkchunk1_s1、chunk2_s1、chunk3_s1每个 chunk 内插入 3 组文档每组含notShardKey前缀分别为 1/2/3 的三条_id从 0 递增以保证$$ROOT测试结果确定性。建立两个索引{shardKey: 1}与{shardKey: 1, notShardKey: 1}后者用于第三个用例的复合索引 DISTINCT_SCAN。切分与迁移对 6 个 chunk 依次执行split再把 shard1 的 3 个 chunk 通过moveChunk迁移到 otherShard。注入孤儿文档向 shard0 插入 shard1 各 chunk 键前缀的孤儿文档${chunk}_${i}_orphan向 shard1 插入 shard0 各 chunk 键前缀的孤儿文档测试文件开头设置了TestData.skipCheckOrphans true明确声明故意插入孤儿。这种双向孤儿布局让每个分片在扫描时都必须借助 shard filtering 剔除不属于自己的文档从而逼出isShardFiltering: true的 DISTINCT_SCAN 计划。三、测试驱动机制两次执行验证 inactive 到 active 的状态流转golden test 的驱动脚本是 shard_filtering_plan_cache.js它对 3 条聚合管道依次调用validateAggPlanCacheUse。该函数定义在 golden_test_utils.jsexport function validateAggPlanCacheUse(coll, pipeline) { coll.getPlanCache().clear(); // 1. 清空计划缓存 subSection(DISTINCT_SCAN stored as inactive plan); runAggAndOutputPlanCacheStats(coll, pipeline); // 2. 首次执行计划被缓存为 inactive subSection(DISTINCT_SCAN used as active plan); runAggAndOutputPlanCacheStats(coll, pipeline); // 3. 再次执行缓存条目转为 active }对应的输出函数runAggAndOutputPlanCacheStatsgolden_test_utils.js先打印完整管道 JSON再执行aggregate命令最后调用outputPlanCacheStats通过$planCacheStats聚合阶段读取缓存状态。outputPlanCacheStatsgolden_test_utils.js有两个关键细节只保留cachedPlan、planCacheKey、createdFromQuery、isActive、shard五个字段其余一律剔除保证 golden 输出稳定若所有条目都带shard字段分片场景则按分片名分组输出——这就是文档中每个分片一个### shard_filtering_plan_cache-rs0 / -rs1小节的原因。inactive/active 语义计划首次执行后存入缓存并标记为isActive: false处于试用状态当相同形状shape的查询再次命中该缓存条目时计划被复用并标记为isActive: true。文档中两次输出isActive从false变为true、而planCacheKey保持不变正是这一机制的直观证据。四、用例一$group 按分片键 $top4.1 管道定义[ { $group : { _id : $shardKey, accum : { $top : { sortBy : { shardKey : 1 }, output : $shardKey } } } } ]该管道按shardKey分组并在每个分组内取shardKey升序排序下的第一个值$top等价于$first。由于分组键就是索引{shardKey: 1}的前缀且结果只需每个不同键值的首条记录查询规划器选择 DISTINCT_SCAN 作为最优计划。4.2 计划缓存条目首次执行后inactive以下为shard_filtering_plan_cache-rs0的完整缓存条目rs1 内容完全一致仅shard字段不同{ cachedPlan : { inputStage : { inputStage : { direction : forward, indexBounds : { shardKey : [ [MinKey, MaxKey] ] }, indexName : shardKey_1, indexVersion : 2, isFetching : false, isMultiKey : false, isPartial : false, isShardFiltering : true, isSparse : false, isUnique : false, keyPattern : { shardKey : 1 }, multiKeyPaths : { shardKey : [ ] }, stage : DISTINCT_SCAN }, stage : SORT_KEY_GENERATOR }, stage : PROJECTION_COVERED, transformBy : { } }, createdFromQuery : { distinct : { key : shardKey }, projection : { _id : 0, shardKey : 1 }, query : { }, sort : { } }, isActive : false, planCacheKey : 70F2D973, shard : shard_filtering_plan_cache-rs0 }4.3 缓存条目的三层结构解读createdFromQuery计划缓存以查询形状为键。$group聚合会被改写为底层的 distinct 查询distinct.key shardKey、投影{_id: 0, shardKey: 1}、空过滤条件与空排序。这是聚合优化器把$group $top等价改写为 distinct 语义的证据。cachedPlan缓存的执行计划自底向上为三层DISTINCT_SCAN扫描索引shardKey_1indexBounds为全区间[MinKey, MaxKey]方向forwardisShardFiltering: true表明该索引扫描阶段同时执行分片过滤isFetching: false表示输出可直接由索引键覆盖无需回表取文档isUnique/isMultiKey/isPartial/isSparse均为 false说明这是一个普通非唯一、非多键索引。SORT_KEY_GENERATOR为$top的排序语义生成排序键。PROJECTION_COVERED被投影覆盖transformBy: {}无需额外文档获取。planCacheKey/isActive/shard70F2D973是该查询形状的缓存键isActive: false表示首次存入、尚未被复用shard标识条目所属分片。第二次执行后除isActive变为true外其余字段完全一致——计划被命中并激活。五、用例二$sort 降序 $group $first5.1 管道定义[ { $sort : { shardKey : -1 } }, { $group : { _id : $shardKey, accum : { $first : $shardKey } } } ]与用例一不同这里显式先按shardKey降序排序再按shardKey分组取每组第一个值。由于排序键与分组键都是shardKey规划器仍然选择 DISTINCT_SCAN但扫描方向变为backward。5.2 计划缓存条目inactive 状态{ cachedPlan : { inputStage : { inputStage : { direction : backward, indexBounds : { shardKey : [ [MaxKey, MinKey] ] }, indexName : shardKey_1, indexVersion : 2, isFetching : false, isMultiKey : false, isPartial : false, isShardFiltering : true, isSparse : false, isUnique : false, keyPattern : { shardKey : 1 }, multiKeyPaths : { shardKey : [ ] }, stage : DISTINCT_SCAN }, stage : SORT_KEY_GENERATOR }, stage : PROJECTION_COVERED, transformBy : { } }, createdFromQuery : { distinct : { key : shardKey }, projection : { _id : 0, shardKey : 1 }, query : { }, sort : { shardKey : -1 } }, isActive : false, planCacheKey : 2ABB9AD5, shard : shard_filtering_plan_cache-rs0 }5.3 与用例一的差异对比direction从forward变为backwardindexBounds从[MinKey, MaxKey]变为[MaxKey, MinKey]——排序方向被完整保留在缓存计划中createdFromQuery.sort记录了{shardKey: -1}说明查询形状包含排序信息planCacheKey变为2ABB9AD5——排序方向不同导致查询形状不同进而产生不同的缓存键其余结构DISTINCT_SCAN → SORT_KEY_GENERATOR → PROJECTION_COVERED、isShardFiltering: true保持不变。第二次执行后isActive转为trueplanCacheKey依旧为2ABB9AD5。六、用例三复合排序 过滤 $group 取 $$ROOT6.1 管道定义[ { $sort : { shardKey : 1, notShardKey : 1 } }, { $match : { shardKey : { $gt : chunk1_s0 } } }, { $group : { _id : $shardKey, r : { $last : $$ROOT } } } ]这是最复杂的一个用例先按shardKey、notShardKey复合升序排序再用$match过滤掉shardKey chunk1_s0的文档最后按shardKey分组、每组取最后一条完整文档$$ROOT。此时 DISTINCT_SCAN 落在复合索引shardKey_1_notShardKey_1上。6.2 计划缓存条目inactive 状态{ cachedPlan : { inputStage : { direction : backward, indexBounds : { notShardKey : [ [MaxKey, MinKey] ], shardKey : [ ({}, \chunk1_s0\) ] }, indexName : shardKey_1_notShardKey_1, indexVersion : 2, isFetching : true, isMultiKey : false, isPartial : false, isShardFiltering : true, isSparse : false, isUnique : false, keyPattern : { notShardKey : 1, shardKey : 1 }, multiKeyPaths : { notShardKey : [ ], shardKey : [ ] }, stage : DISTINCT_SCAN }, stage : SORT_KEY_GENERATOR }, createdFromQuery : { distinct : { key : shardKey }, projection : { }, query : { shardKey : { $gt : chunk1_s0 } }, sort : { notShardKey : 1, shardKey : 1 } }, isActive : false, planCacheKey : 7724B971, shard : shard_filtering_plan_cache-rs0 }6.3 本用例的新增要点复合索引下的双向 indexBoundsshardKey区间为({}, chunk1_s0)——左开右闭对应$gt: chunk1_s0的过滤条件notShardKey区间为[MaxKey, MinKey]对应$last所需的逆向扫描isFetching: true由于输出需要整条文档$$ROOT仅靠索引键无法覆盖必须回表取文档因此与用例一、二isFetching: false形成鲜明对比createdFromQuery.query记录了{shardKey: {$gt: chunk1_s0}}证明$match条件参与查询形状计算并被用于生成索引边界结构上少了最外层的PROJECTION_COVERED阶段因为需要取整条文档只剩DISTINCT_SCAN → SORT_KEY_GENERATOR两层planCacheKey为7724B971同样在第二次执行后isActive变为true其余字段不变。七、字段速查表计划缓存条目核心字段字段含义本测试中的取值cachedPlan缓存的执行计划树自底向上DISTINCT_SCAN → SORT_KEY_GENERATOR →可选PROJECTION_COVEREDcachedPlan.inputStage.stage最底层扫描阶段DISTINCT_SCANcachedPlan.inputStage.isShardFiltering是否在该阶段执行分片过滤剔除孤儿文档恒为truecachedPlan.inputStage.indexName使用的索引名shardKey_1/shardKey_1_notShardKey_1cachedPlan.inputStage.direction索引扫描方向forward$top/$first 升序或backward$last 或显式降序cachedPlan.inputStage.indexBounds各索引键的扫描区间[MinKey, MaxKey]/[MaxKey, MinKey]/({}, chunk1_s0)cachedPlan.inputStage.isFetching是否需要回表取文档输出为$$ROOT时为true仅取 shardKey 时为falsecachedPlan.inputStage.indexVersion索引格式版本2createdFromQuery生成该缓存的原始查询形状distinct/projection/query/sort 四要素isActive缓存条目是否已被复用激活首次执行false再次执行trueplanCacheKey查询形状对应的缓存键哈希70F2D973/2ABB9AD5/7724B971shard缓存条目所属分片shard_filtering_plan_cache-rs0/-rs1八、特性开关与预期输出的关系本测试的预期输出同时存在于两个目录expected_output/shard_filtering_plan_cache.md默认路径expected_output/featureFlagSbeFull/shard_filtering_plan_cache.md本文主分析的 SBE full 特性开关路径。对比两者可见JSON 结构完全一致唯一差异是planCacheKey的哈希值例如用例一由BA82D8EA变为70F2D973用例二由63EF7895变为2ABB9AD5用例三由816434D2变为7724B971。这印证了 golden test 输出中同时保留默认与特性开关两套快照、以覆盖不同引擎组合下缓存键稳定性的做法。测试脚本通过tags声明了featureFlagGetExecutorDeferredEngineChoice与featureFlagShardFilteringDistinctScan两个特性开关以及requires_fcv_82的 FCV 门槛实际运行时由 resmoke 套件见 jstests/query_golden_sharding 目录下的套件配置按标签组合分别驱动。九、如何复现与运行该 golden testgolden test 是 MongoDB 内部测试体系resmoke bazel的一部分运行前需要具备构建好的 mongod/mongos 二进制与测试基础设施。相关复现路径如下测试入口jstests/query_golden_sharding/shard_filtering_plan_cache.js可直接作为 resmoke 的测试文件参数依赖工具库golden_test_utils.js提供validateAggPlanCacheUse、outputPlanCacheStats等核心断言/输出函数golden_sharding_utils.js提供双分片 孤儿文档的分片环境搭建pretty_md.js负责 Markdown 章节与代码块的排版输出analyze_plan.js提供 explain 结果解析与稳定化工具预期输出比对运行测试时框架会把实际输出与expected_output/下的 Markdown 快照逐字比对任何计划结构、缓存键或状态变化都会导致测试失败从而锁定规划器行为。运行时测试会先在两个分片上分别创建shardKey_1与shardKey_1_notShardKey_1索引插入带孤儿的数据然后对三条管道各执行两轮聚合清缓存 → 首次执行 → 二次执行最终以分片为单位输出计划缓存快照。你可以通过阅读outputPlanCacheStats的实现golden_test_utils.js精确理解快照的生成与字段裁剪规则。十、结论与工程启示通过这份 golden test 预期输出可以得出以下几点可验证的工程结论DISTINCT_SCAN 可与分片过滤共存在含孤儿 chunk 的分片集合上DISTINCT_SCAN 阶段通过isShardFiltering: true同时完成去重扫描与孤儿过滤保证结果不越界返回不属于本分片的数据计划缓存按查询形状精确隔离过滤条件query、排序sort、投影projection任一变化都会产生独立的planCacheKey而相同形状的二次执行仅翻转isActive标志不重建计划聚合优化会改写为 distinct 语义$group $top/$first/$last等按分片键分组的管道在createdFromQuery中呈现为底层的 distinct 查询这正是 DISTINCT_SCAN 得以被选中的前提覆盖性差异体现在isFetching只需索引键的输出isFetching: false与需要整条文档$$ROOT的输出isFetching: true决定了缓存的执行计划是否保留 PROJECTION_COVERED 顶层阶段。对于 MongoDB 内核开发者与高级 DBA 而言这份预期输出既是理解分片 去重 计划缓存三者交互的最小可读样例也是验证规划器行为回归的权威基准。【免费下载链接】mongoThe MongoDB Database项目地址: https://gitcode.com/GitHub_Trending/mo/mongo创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
延伸阅读

更多相关文章

2026/9/14 12:24:34

STM32F103 ADC电压读取完整工程:采样周期、滤波与注入通道

简介:这个工程覆盖了基于STM32F103的模拟电压采集完整链路,包括GPIO模拟输入配置、ADC校准、12位采样精度设置、单次/连续/扫描模式切换、采样时间匹配,以及通过查询、中断或DMA读取转换结果并换算为实际电压值,特别适合刚接触STM…

2026/9/14 12:24:34

2026年程序员兼职平台选择与报价策略全解析

1. 程序员兼职现状与平台选择逻辑2026年的程序员兼职市场已经形成了明显的分层结构。从我的实际接单经验来看,目前主流的接单渠道可以分为三类:国际平台、国内垂直平台和私域流量渠道。每种渠道都有其独特的运作规则和收益天花板。国际平台以Upwork、Top…

2026/9/14 13:09:38

WorkBuddy Enterprise:企业级智能体操作系统架构与落地实践

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

2026/9/14 13:09:38

CESM移植实战:Machine File配置与性能调优指南

1. CESM移植概述:为什么需要定制Machine FileCESM(Community Earth System Model)作为地球系统建模领域的标杆工具,其移植工作常让研究者头疼不已。不同于常规软件的直接编译安装,CESM对目标机器的环境有严苛要求&…

2026/9/14 13:09:38

SpringBoot中JWT与Sa-Token认证方案对比与实践

1. 项目概述 在当今的企业级应用开发中,认证与鉴权是保障系统安全的核心环节。SpringBoot作为Java生态中最流行的微服务框架,如何选择合适的认证鉴权方案一直是开发者面临的重要决策。JWT(JSON Web Token)作为无状态认证的事实标准,与新兴的S…

2026/9/14 13:09:38

marketing skills开源项目:把SEO和增长技能封装成Agent命令行工具

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

2026/9/14 13:09:38

MATLAB实现六参数开普勒轨道计算ECI位置速度

简介:本资源是一份面向航天动力学初学者与MATLAB实践者的卫星轨道参数转换工具包,聚焦于从经典轨道要素(COE)精确计算任意时刻的卫星位置与速度矢量,适用于轨道分析、任务规划及航天课程实验等场景。压缩包为1KB的ZIP文…

2026/9/14 13:04:38

多Agent协作如何通信?hermes peer协议与全栈实战解析

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

2026/9/14 2:17:50

拯救者Y7000黑屏故障排查与维修实战指南

1. 项目概述:一台黑屏的拯救者Y7000,到底卡在哪一步? 联想拯救者Y7000系列笔记本,从2018年第一代搭载i5-8300H开始,到后来的i7-9750H、i7-10750H、i5-11400H,再到2023年款的R7-7840HS,它始终是学…

2026/9/14 0:03:22

KCF目标跟踪算法与OTB工程实现:毕业设计实战解析

简介:这是一份基于KCF核相关滤波算法、融合尺度池与抗遮挡处理的目标检测跟踪MATLAB完整源码,主要面向计算机相关专业准备毕业设计、课程设计或期末大作业的学生,也适合需要项目实战练习的初学者。源码在OTB数据集上完成验证,能够…

2026/9/14 0:03:22

语音情感识别实战:Keras实现LSTM、CNN、SVM与MLP多模型对比

简介:面向语音情感识别入门与进阶开发者,这份基于Keras的项目源码完整实现了LSTM、CNN、SVM、MLP四种模型,兼容Python3.8与Keras/TensorFlow2环境。压缩包内含49个文件,大小约70.31MB,主体包括Python脚本、yaml/json配…

2026/9/14 11:59:31

USB Type-C PCB布局分区设计:电源、高速信号与PD协议全攻略

做硬件这行,Type-C接口算是典型的“看着简单,做起来全坑”的东西。光引脚就24个,高低速信号、电源、控制线全部塞在一个小小的连接器里,如果PCB布局不做规划,打样回来基本就是“插上没反应”、“高速掉线”、“静电一打…

2026/9/12 14:32:17

系统编程学习原型如何补齐稳定性边界

系统编程学习原型如何补齐稳定性边界预算有限时&#xff0c;我先优化明显多余的复制&#xff0c;而不是猜测性地换容器。用借用传递只读数据通常就能减少分配&#xff1a; fn parse(line: &str) -> Result<Item, Error> { /* ... */ }用基准确认热点确实在分配&am…

2026/9/14 11:22:57

雨花区哪家财务公司代理记账比较好?

在雨花区&#xff0c;企业处理财税事务常常面临诸多挑战&#xff0c;选择一家靠谱的财务公司至关重要。湖南巨勤财务管理咨询有限公司就是本地正规实体财税服务机构&#xff0c;深耕本地工商财税行业多年&#xff0c;熟悉当地工商局、税务局最新政策与申报流程。主营公司注册、…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
咨询二维码