bigywater.tech
MongoDB · 73

chunk 拆分与迁移:balancer 实际搬了什么

chunk 是怎么拆分、怎么被 balancer 搬走的?chunk 个数不平衡算不算失衡?迁走的数据什么时候删?这篇在 2 个分片的集群上把 chunk 大小设为 1 MB,写入 60 MB 数据,观察分布变化、迁移步骤和 jumbo chunk。

1 对 26
最终两个分片的 chunk 个数,但拥有的数据量是 31.1 MB 对 28.2 MB
3600 秒
orphanCleanupDelaySecs:迁走的数据在源分片上延迟这么久才删(之前是孤儿文档)
6 个步骤
config.changelog 中 moveChunk.from 记录的迁移步骤,一次 1 MB 的 chunk 迁移约 0.25 秒

起点

实测 · 本机执行输出
  # config.settings
  test> db.getSiblingDB("config").settings.find()
  [ { _id: 'chunksize', value: 1 } ]
  [('sh0', 'sh0/127.0.0.1:9902'), ('sh1', 'sh1/127.0.0.1:9903')]

刚分片的空集合只有一个 [MinKey, MaxKey) 的 chunk,在 primary 分片上;chunk 大小设成 1 MB 便于在小数据上观察。

分裂与迁移

实测 · 本机执行输出
  # config.chunks
  test> db.getSiblingDB("config").chunks.find({}, {_id: 0, min: 1, max: 1, shard: 1})
  [ { min: { k: MinKey() }, max: { k: MaxKey() }, shard: 'sh0' } ]

写入触发 chunk 分裂,balancer 同时把新 chunk 迁到另一个分片。分裂由写入流量触发:写入停止后,剩下的一个大 chunk 不会再被拆。

按数据量平衡

实测 · 本机执行输出
  写入期间 chunk 分布: 0s:{'sh0': 1} → 2s:{'sh0': 1} → 4s:{'sh0': 1} → 6s:{'sh0': 1, 'sh1': 1} → 8s:{'sh0': 1, 'sh1': 3} → 10s:{'sh0': 1, 'sh1': 4}
  写完时(耗时 12 s):{'sh0': 1, 'sh1': 6}

最终 chunk 个数是 1 对 26,但两个分片拥有的数据量是 31.1 MB 对 28.2 MB,balancer 认为已经平衡。

孤儿文档

实测 · 本机执行输出
  写完之后: 0s:{'sh0': 1, 'sh1': 6} → 10s:{'sh0': 1, 'sh1': 14} → 20s:{'sh0': 1, 'sh1': 21} → 30s:{'sh0': 1, 'sh1': 26} → 40s:{'sh0': 1, 'sh1': 26} → 45s:{'sh0': 1, 'sh1': 26}
  最终 chunk 分布 {'sh0': 1, 'sh1': 26}(chunk 个数差很多,但看数据量)
    sh1:拥有 28,525 个文档 / 28.2 MB;孤儿文档 0 个 / 0.0 MB
    sh0:拥有 31,475 个文档 / 31.1 MB;孤儿文档 26,312 个 / 26.0 MB
    经 mongos 总数 60,000;直连分片的文档数 sh0=57,787、sh1=28,525(含孤儿文档,所以之和大于总数;mongos 会过滤孤儿)
  # 孤儿文档的清理延迟(秒)
  test> db.adminCommand({getParameter: 1, orphanCleanupDelaySecs: 1}).orphanCleanupDelaySecs
  3600

迁走的数据在源分片上不会立刻删除,直连源分片看到的文档数比 mongos 看到的多;orphanCleanupDelaySecs 默认 3600 秒。

迁移的步骤

实测 · 本机执行输出
  moveChunk.from 事件共 26 条
  其中一条:from sh0 → to sh1;min={'k': 259913947} max={'k': 276259863}
    各步骤耗时(毫秒): step 1 of 6: 0, step 2 of 6: 0, step 3 of 6: 13, step 4 of 6: 110, step 5 of 6: 21, step 6 of 6: 91
    其它字段: ['from', 'max', 'min', 'note', 'to']

每次迁移在 config.changelog 里记录 6 个步骤的耗时;1 MB 的 chunk 迁移总共不到 0.25 秒。

jumbo chunk

实测 · 本机执行输出
  test.j:4000 个文档(约 4 MB)全部 k = 7;chunk 数 1;其中带 jumbo 标记的 1
  chunk 范围: [({'k': MinKey()}, {'k': MaxKey()}, 'sh0')]

分片键全相同的数据没法拆分,chunk 超过大小上限却不能拆,被标记为 jumbo,无法被 balancer 迁移。

分步演示

chunk 拆分与迁移
本步的对象

    本文用到的工具与边界

    • 本机 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、时间戳等每次运行都不同。2 个分片(各为单节点副本集)+ 1 个 config server + 1 个 mongos;chunk 大小设成 1 MB(默认 128 MB)
    • 没有验证:默认 128 MB chunk 下的行为;三个及以上分片的平衡;orphanCleanupDelaySecs 到期后孤儿文档的清理;jumbo chunk 的手工处理(清除标记、增加分片键后缀);autoMerger 与 defragmentation;balancer 的窗口与限流参数(balancerMigrationsThrottlingMs)
    京ICP备2026067502号-1京公网安备11010502063487号