ARTICLE DETAIL

资讯详情

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

MongoDB 分片时序集合实现详解:shardCollection 分片键转换、块路由与 Bucket 集合的 CRUD/DDL 调用链

MongoDB 分片时序集合实现详解:shardCollection 分片键转换、块路由与 Bucket 集合的 CRUD/DDL 调用链 MongoDB 分片时序集合实现详解shardCollection 分片键转换、块路由与 Bucket 集合的 CRUD/DDL 调用链【免费下载链接】mongoThe MongoDB Database项目地址: https://gitcode.com/GitHub_Trending/mo/mongo本文围绕 MongoDB 仓库中的 Sharded Time-Series Collections 设计文档 展开系统讲解分片时序集合的完整实现机制如何在 view 命名空间上执行shardCollection、分片键如何转换为 buckets 集合的control.min/max字段、块chunk边界为何与 measurement 位置脱钩以及 mongos 与 shard 上 CRUD、聚合、DDL 请求的翻译与路由调用链。读完你可以掌握分片键的三类限制与转换规则、timeseriesFields元数据的权威来源、chunk 重叠示例的推演方法以及setBucketNss、isTimeseriesViewRequest等关键源码入口。背景viewful / viewless 时序集合与 buckets 集合在理解分片实现之前需要先建立时序集合的基本模型。MongoDB 的时序集合对外提供直接读写 measurement的简单接口实际数据则按时间窗口组织在buckets桶中。一个默认viewful的时序集合mydb.mytscoll在目录中由两部分表示见 src/mongo/db/timeseries/README.md非物化视图mydb.mytscoll以 buckets 集合为源集合。允许在 view 上执行插入每个文档必须包含 timeField查询 view 时会隐式解包unpack底层 bucket 数据返回原始非桶化的文档。解包由聚合阶段$_internalUnpackBucket完成。系统集合mydb.system.buckets.mytscoll实际数据的存放位置。每个文档代表一段时间内的 measurement 集合若创建时定义了 metaField则同一 bucket 内所有 measurement 共享相同 meta 值bucket 还受 measurement 总数与文档大小约束。buckets 文档的核心字段是control.min.timeField/control.max.timeField时间下界按 granularity 向下取整与meta字段。对于分片时序集合分片机制完全作用在 buckets 集合上——这正是本文档的主题。文档中提到的查询重写与$_internalUnpackBucket细节仓库在 src/mongo/db/query/timeseries/README.md 中有专门说明本文不展开。时序集合还存在viewless形态时序数据与 buckets 使用同一命名空间不经过 view 层。该形态的文档尚在更新中源码文档里标注了TODO (SERVER-102458)本文按当前仓库文档的既有描述为准。创建分片时序集合shardCollection 命令与分片键转换在 view 命名空间上执行 shardCollection用户通过在view命名空间上运行带timeseries选项的shardCollection命令来创建分片时序集合若集合尚不存在该命令会隐式创建它。分片键模式在满足既有分片键限制之外还必须满足以下时序专属限制只能是timeField、metaField和/或metaField的子字段若为复合键timeField必须出现在最后timeField只能使用升序 range 键。关键的实现细节是在 create collection 的 DDL 协调器内部primary shard 会把shardCollection命令转换成作用于 buckets 命名空间的命令并将分片键改写为 buckets 集合上的分片键之后命令按普通的shardCollection流程执行。因此持久化在分片目录sharding catalog中的信息——config.collections里的集合名与分片键、config.chunks里的块边界——引用的都是buckets 命名空间及其元数据而非 view。分片键与索引的转换对照表以下表格完整继承了原文档给出的转换规则示例中timeseries选项为{timeField: t, metaField: m}view 上的分片键用户指定buckets 上的分片键持久化于 config.collectionsbuckets 上的索引{t: 1}{control.min.t: 1}{control.min.t: 1, control.max.t: 1}{m: 1, t: 1}{meta: 1, control.min.t: 1}{meta: 1, control.min.t: 1, control.max.t: 1}{m: hashed}{meta: hashed}{meta: hashed}{m: hashed, t: 1}{meta: hashed, control.min.t: 1}{meta: hashed, control.min.t: 1, control.max.t: 1}{m.a: 1}{meta.a: 1}{meta.a: 1}从表格可以读出转换规律timeField恒被映射为control.min.timeField分片键并在配套索引中追加control.max.timeFieldmetaField及其子字段按字段名直译到 buckets 的meta结构上hashed 类型保持不变。源码佐证转换后的 buckets 命名空间在 DDL 协调层被写入协调器元数据sharding_coordinator.cpp 中可见coordMetadata.setBucketNss(bucketNss)即文档所述的DDL 协调器把操作翻译到 buckets 命名空间并存储于ShardingCoordinatorMetadata。创建协调器对时序字段的处理见 create_collection_coordinator.cpptranslatedRequestParams.getTimeseries()取出timeseriesFields后设置translatedRequest.setTimeseries(...)同文件 L1396-L1397 中通过coll.setTimeseriesFields(timeseriesFields)将其落到目录对象上。分片元数据timeseriesFields 与 granularity 的权威来源config server 上针对 viewful 时序集合只存储 buckets 集合不存 view。每个 buckets 集合在 config server 上都带有一个timeseriesFields参数其内容与timeseriesOptions相同。该参数会被CatalogCache、ChunkManager与集合元数据加载到内存中使用。对 viewless 时序集合而言不存在 view 命名空间config server 的全部元数据同样收敛在这个timeseriesFields参数中。granularity 的权威性规则如果通过collMod更新了 granularityconfig server 上的timeseriesFields参数会同步更新并且以 config server 上的值作为该集合 granularity 的 source of truth。这一点至关重要的原因是bucket 的control.min.timeField是由文档timeField值按 granularity 向下取整round down得到的当分片键落在timeField上时文档按control.min.timeField路由到 shard而该值依赖 granularity。因此在collMod执行期间所有查询都必须使用更新后的 granularity 值。实现上在执行任何 CRUD 或聚合之前mongos 会通过缓存的CollectionRoutingInfo检查 config server 上的 granularity从而保证操作始终使用最新的 granularity谓词predicate也能被正确路由。从源码结构看分片目录中对timeseriesFields的引用也印证了这一参数是路由信息的一部分例如 sharding_catalog_manager.cpp 附近以timeseriesFields.timeField动态构造 view 定义的字段模式。Chunk 的格式块边界定义的是 bucket不是 measurement分片键选择建议分片键可以落在metaField或metaField的任意子字段上。这是文档推荐的方案因为用户应选择能较均匀地切分 measurement 的metaField相比之下timeField的值单调递增可能把所有插入都路由到同一个 shard。块范围与 measurement 位置的脱钩当分片键落在timeField上时块范围定义在 buckets 集合的control.min.timeField字段上。control.min.timeField是 bucket 的向下取整下界——完全可能且很可能没有任何 measurement 恰好等于这个值。与普通分片集合不同measurement 的位置并不与块范围强绑定块范围定义的是bucket而非 measurement应位于哪里。在普通分片集合中块范围互不重叠可以假设具有某特定分片键值的文档只存在于一个块上但时序集合的 bucket 范围是会重叠的同一个 bucket 可以属于不同块。这意味着 measurement 的值可以超出其所在块的边界而仍然存在于该块中。原文档示例完整推演以分片键为timeFieldtimeField time为例// 我们有如下 measurement Doc1 {time: TimeStamp(10000), A:10, B:11, C:12} Doc2 {time: TimeStamp(25000), A:20, B:21, C:22} Doc3 {time: TimeStamp(30000), A:30, B:31, C:32} // 我们有 3 个 bucket Bucket1 {control.min.time: TimeStamp(10000), control.max.time: TimeStamp(25000), bucketed measurements} Bucket2 {control.min.time: TimeStamp(15000), control.max.time: TimeStamp(30000), bucketed measurements} Bucket3 {control.min.time: TimeStamp(20000), control.max.time: TimeStamp(35000), bucketed measurements} // 我们有如下可能的块集合 Chunk0 range: {control.min.time: MinKey, control.min.time: TimeStamp(10000)} Chunk1 range: {control.min.time: TimeStamp(10000), control.min.time: TimeStamp(20000))} Chunk2 range: {control.min.time: TimeStamp(20000), control.min.time: TimeStamp(30000)} Chunk3 range: {control.min.time: TimeStamp(30000), control.min.time: MaxKey} // 各块包含的 bucket 与 measurement Chunk0 contains no buckets Chunk1 contains Bucket1 and Bucket2. Bucket1 contains Doc1. Bucket2 contains Doc2. Chunk2 contains Bucket3. Bucket3 contains Doc3. Chunk3 contains no buckets推演要点Chunk1的范围是[10000, 20000)按control.min.time划分Bucket2control.min.time 15000落在其中因此被Chunk1包含但Bucket2内的Doc2time 25000超出了 Chunk1 的块边界只是恰好落在 bucket 内部Bucket2同时横跨Chunk1/Chunk2的边界区间其时间范围[15000, 30000)与两块都有交集体现bucket 范围会重叠并可属于不同块。结论时序集合中块边界不定义measurement 的存储位置只定义 bucket 的路由归属。查询时 mongos 需要把谓词改写成 bucket 级别的比较control.min/max区间判断而不是像普通集合那样直接定位分片键值的块。CRUD 操作的路由view 请求如何落到 buckets对于 insert/update/delete 请求mongos 在view命名空间上收到请求后先检查ChunkManager是否已有 buckets 集合的路由表routing table。若没有则检查CatalogCache中是否存在 buckets 集合文档指出参考CollectionRoutingInfoTargeter::_init。在任一位置找到 buckets 集合后mongos 执行四步翻译命名空间翻译把请求改写为作用于 buckets 集合命名空间提取分片键取出 buckets 集合的分片键置位标志将isTimeSeriesNameSpace标志设为true谓词重写仅 update 和 delete调用getBucketLevelPredicateForRouting重写查询谓词——字段名被改写以匹配 buckets 结构metaField变为metatimeField变为control.min.timeField与control.max.timeField。重写后的谓词用于路由决策。上述步骤使 mongos 能够决定目标 shard 集合或决定广播命令。典型场景分片键在metaField上且谓词不含metaField第 4 步不会重写谓词向分片键提取器传入空对象→ 触发 update/delete 广播分片键在timeField上且谓词不含timeField同样传入空对象 → 广播。shard 侧的处理请求被路由或广播后各 shard 收到请求并检查isTimeSeriesNameSpace是否已置位见timeseries::isTimeseriesViewRequest。若置位shard 便调用与非分片时序集合相同的时序专用函数处理。例如 insertmeasurement 依次尝试写入 bucket catalog 中的 open bucket、reopened bucket必要时新开 bucketupdate 和 delete 逐个 bucket 处理必要时对 bucket 执行解包unpack。仓库中的源码入口可以印证这条链路timeseries_request_util.h 定义了isTimeseriesViewRequestT模板它从请求中取 namespace或 UUID、isTimeseriesNamespace标志结合lookupTimeseriesCollection的时序信息判断isTsViewRequest lookupTimeseriesInfo.isTimeseries (wasNssTranslated || isTimeseriesNamespaceFlag)。该头文件中的 TODOSERVER-101784说明此函数计划在仅存在 viewless 集合后移除——从源码结构看viewful 形态正处在向 viewless 演进的过渡期。写路径在执行层消费该判断write_ops_exec.cpp 中isTimeseriesViewRequest作为参数贯穿 update 的执行逻辑如 L2482if (isTimeseriesViewRequest)分支与时序写路径的转换/解包流程衔接。bucket 级谓词生成相关实现位于 src/mongo/db/query/timeseries/ 目录如 bucket_level_comparison_predicate_generator.h 与 timeseries_translation.cpp对应把 view 谓词翻译为 bucket 级比较谓词这一能力。聚合查询路由viewful 与 viewless 两条路径Viewful 时序集合流程与普通分片集合上的 view 查询类似用户在view命名空间上发起聚合mongos 把查询路由到 primary shardprimary shard 解析 view把查询改写为作用于 buckets 集合并抛出CommandOnShardedViewNotSupportedOnMongod错误错误消息中携带完整的 pipeline view 定义。mongos 捕获该错误后获得展开后的 view 定义然后按常规方式路由查询。关键设计点处理时序 bucket 集合的聚合阶段$_internalUnpackBucket会被下推到 shard 执行使各 shard 以桶化形态做本地计算而非把原始 measurement 全部拉回 mongos。Viewless 时序集合Viewless 集合文档标注TODO (SERVER-102458)待更新在路由查询前会被直接改写为作用于 buckets 本身并设置rawData参数为true。所应用的查询重写规则见 src/mongo/db/query/timeseries/README.md。DDL 操作buckets 集合对用户的隐身用户对view命名空间执行 DDL 操作collMod、createIndexes、listIndexes、dropIndexes等。buckets 集合在语义上对用户不可见直接对它执行 DDL 需要特殊权限。调用链与关键实现DDL 协调器翻译协调器使用setBucketNss函数把操作翻译到 buckets 命名空间存入ShardingCoordinatorMetadata并置位isTimeseriesNamespace标志各具体 DDL 协调器按需做进一步时序改写。例如CreateCollectionCoordinator会检查ChunkManager中是否存在timeseriesFields据此决定转发到 shard 前是否需要重写分片键。shard 侧再决策shard 收到 DDL 操作后自行判断是否要把操作体翻译到 buckets 命名空间。例如listIndexes依据isTimeseriesNamespace标志返回 buckets 集合上的全部索引。反向翻译某些操作必须做buckets → view的反向翻译才能返回给用户。listIndexes返回的索引由shardCollection/createIndexes创建会先从 buckets 集合索引翻译回时序 view 形态再返回目的正是维持 buckets 集合的不可见性。这与 src/mongo/db/timeseries/README.md 中{meta: 1}在 buckets 上、listIndexes呈现为{mm: 1}的索引呈现规则一致。分片管理命令唯一直接面向 buckets 的命令族所有分片管理命令——如split、moveChunk——必须直接在buckets集合上执行而不是 view 命名空间。这些是极少数用户需要直接操作 buckets 命名空间的命令与view 对用户唯一可见的原则形成明确例外。运维含义做块管理splitAt 的边界值、moveChunk 的 target shard 选择时边界值需按 buckets 的分片键形态如control.min.timeField或meta给出而不是用户 measurement 字段。Orphan buckets 与 BucketCatalog 的一致性处理BucketCatalog在内存中保存 open buckets 集合到来的 measurement 会插入其中的 open bucket。在分片场景下chunk 迁移migration可能使某个 bucket 在源 shard 上成为orphan块已迁走bucket 文档仍留在原 shard。此时BucketCatalog不能再把新 measurement 写入这些已孤儿化的 bucket。实现方案chunk 迁移成功后BucketCatalog会被 clear。源码中对应的接口在 bucket_catalog.hclear的注释说明它会清除因集合 UUID 被清理而从目录移除的 bucket使这些 bucket 可以被安全地从磁盘状态重新打开reopen。这样孤儿 bucket 退化为普通磁盘文档后续对它的写入走标准的 query-based reopening 路径而非内存目录直写避免了向已迁移块写入新 measurement 的数据错置。小结分片时序集合的三张翻译表从源码结构看整个机制可以概括为三组对称的翻译理解了它们就掌握了本模块的全部路由逻辑翻译方向位置内容view 分片键 → buckets 分片键DDL 协调器primary shardCreateCollectionCoordinator按前文对照表改写 shard key 与配套索引view 命名空间/谓词 → buckets 命名空间/谓词mongosgetBucketLevelPredicateForRouting等与 DDLsetBucketNss插入原样落 bucketsupdate/delete 重写为meta/control.min/control.max谓词buckets 元数据 → view 形态shard 返回前如listIndexes索引键、集合元数据反向翻译维持 buckets 不可见再加上两条一致性保障——granularity 以 config servertimeseriesFields为权威来源、chunk 迁移后 clearBucketCatalog防止孤儿写入——构成了分片时序集合从创建、写入、查询到块运维的完整闭环。延伸阅读仓库内路径src/mongo/db/global_catalog/README_timeseries.md本文主体文档src/mongo/db/timeseries/README.md时序集合总体设计bucket schema、BucketCatalog、granularity 预设、更新/删除语义src/mongo/db/query/timeseries/README.md$_internalUnpackBucket与查询重写src/mongo/db/global_catalog/ddl/sharding_coordinator.cppsetBucketNss写入协调器元数据src/mongo/db/timeseries/timeseries_request_util.hisTimeseriesViewRequest判定逻辑src/mongo/db/query/write_ops/write_ops_exec.cpp写执行层对时序 view 请求的分支处理。【免费下载链接】mongoThe MongoDB Database项目地址: https://gitcode.com/GitHub_Trending/mo/mongo创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表