bigywater.tech
MongoDB · 76

mongos 路由:targeted 与 scatter-gather

一条查询是怎么被 mongos 路由到分片的?带不带分片键差多少负载?这篇用 3 个分片逐种查询看 explain 的 stage、涉及的分片,再统计 100 次查询带给每个分片的负载。

1 对 3
带分片键的等值查询与不带分片键的查询涉及的分片数
SHARD_MERGE_SORT
sort + limit 的查询在 mongos 上的阶段:各分片排序后归并
60 对 1
city = 7 与 uid = 1500 两种查询合计检查的索引键数

targeted

实测 · 本机执行输出
  sh0  [MinKey(), 1000)
  sh1  [1000, 2000)
  sh2  [2000, MaxKey())

条件里有分片键的等值或落在单个 chunk 范围内的范围查询,mongos 只问一个分片(SINGLE_SHARD)。

scatter-gather

实测 · 本机执行输出
  uid = 1500                  stage=SINGLE_SHARD  分片 ['sh1']
  uid ∈ [500, 800)            stage=SINGLE_SHARD  分片 ['sh0']
  uid ∈ [900, 1100)(跨 sh0、sh1)stage=SHARD_MERGE   分片 ['sh0', 'sh1']
  uid $in [10, 1100, 2100]    stage=SHARD_MERGE   分片 ['sh0', 'sh1', 'sh2']
  city = 7(不含分片键)             stage=SHARD_MERGE   分片 ['sh0', 'sh1', 'sh2']
  空条件(全表)                     stage=SHARD_MERGE   分片 ['sh0', 'sh1', 'sh2']
  city = 7, sort uid          stage=SHARD_MERGE_SORT分片 ['sh0', 'sh1', 'sh2']

跨 chunk 的范围、$in 多值、不含分片键的查询要问所有相关分片(SHARD_MERGE),集群总负载约乘以分片数。

负载

实测 · 本机执行输出
  查询(各 100 次)                      sh0   sh1   sh2
  uid = 1500(targeted)               0   100     0
  uid 范围 [0, 100)                  100     0     0
  city = 7(无分片键)                   100   100   100
  空条件 limit 1                      100   100   100

各 100 次查询时,targeted 查询只给一个分片带来 100 次 query,无分片键的查询给三个分片各带来 100 次。

排序

实测 · 本机执行输出
  stage = SHARD_MERGE_SORT;各分片返回 nReturned: [('sh1', 5), ('sh2', 5), ('sh0', 5)];最终 nReturned 5

sort + limit:每个分片各自排序取 limit 个,mongos 归并后再取前 limit 个。

写入

实测 · 本机执行输出
  update_one 带分片键 uid               成功(matched=1)
  update_one 只带 _id 以外的字段 city      成功(matched=1)
  update_many 不带分片键                 成功(matched=60)
  delete_one 不带分片键                  成功(matched=1)

9.0.2 上不带分片键的 update_one、delete_one 也能成功,服务端会自己找到文档所在的分片。

检查量

实测 · 本机执行输出
  uid = 1500  涉及 1 个分片;每个分片(名, 检查的键, 检查的文档, 返回): [('sh1', 1, 1, 1)];合计检查键 1
  city = 7    涉及 3 个分片;每个分片(名, 检查的键, 检查的文档, 返回): [('sh0', 20, 20, 20), ('sh1', 20, 20, 20), ('sh2', 20, 20, 20)];合计检查键 60

targeted 查询只检查 1 个键,scatter-gather 在每个分片上各检查一遍(这里共 60 个键)。

分步演示

mongos 路由
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / MongoDB 9.0.2(Homebrew 的 mongod、mongos)/ mongosh 2.12.0 / pymongo 4.18.3;每个实验在临时目录里起全新的 mongod(独立 dbpath,端口 9900 以上,只监听 127.0.0.1,WiredTiger 缓存 0.25 GB),需要副本集或分片集群时在本机起多个进程;命令输出来自真实的 mongosh,批量写入、计时与多线程用 pymongo;计时用 time.perf_counter,数字每次运行会有小幅抖动,ObjectId、时间戳等每次运行都不同。3 个分片(各为单节点副本集)+ 1 个 config server + 1 个 mongos;分片范围手工 split 与 moveChunk 得到
    • 没有验证:多个 mongos 之间路由表缓存不一致(StaleConfig 重试)的开销;sort 的内存合并上限;不带分片键的单文档写的实现方式与代价(可能用到内部事务);aggregate 的路由(见后面一篇);读偏好、maxTimeMS 在 scatter-gather 中的作用;mongos 连接池
    京ICP备2026067502号-1京公网安备11010502063487号