jumbo chunk是迁移失败的明确信号,必须先手动拆分;确认方法是执行db.getsiblingdb("config").chunks.find({jumbo:true}).limit(5).toarray(),返回非空即属实;sh.splitat报错说明split点落在全大文档区间,需先微调分片键再split;单文档超16mb属数据污染,2–16mb则引发movechunk超时失败。

直接结论:Jumbo chunk 是迁移失败的明确信号,不是“可能原因”,而是 config.chunks 集合里带 jumbo: true 标记的真实实体。它无法被 moveChunk 搬走,必须先手动拆分,否则均衡器永远绕开它。
怎么确认当前集群真有 jumbo chunk
别只看 sh.status(true) 输出末尾的警告行——它可能滞后或漏报。真实依据只能是 config 库的元数据:
- 连到任意 mongos,执行
db.getSiblingDB("config").chunks.find({jumbo: true}).limit(5).toArray(),只要返回非空数组,就坐实了问题 - 重点看返回文档里的
ns(集合全名)、min和max字段,这是后续操作的坐标 - 如果查不到,但迁移仍卡住,问题大概率不在 jumbo,而是网络或锁住的迁移任务
为什么 sh.splitAt() 常常报错“cannot split chunk containing jumbo documents”
这个错误不是命令写错了,而是 MongoDB 在告诉你:你选的 split 点落在了一个“全塞满大文档”的 key 区间里。典型场景是分片键基数极低(比如用 status 字段分片,值只有 "active"/"inactive" 两种),导致几十万条记录挤在同一个 {status: "active"} 范围内,而其中混着几个 10MB 的文档。
- 此时
sh.splitAt("db.col", {status: "active"})会失败,因为整个 chunk 的 min/max 都是{status: "active"} - 正确做法是先让数据“松动”:用
db.col.updateMany({status: "active", _id: {$lt: ObjectId("...")}}, {$set: {status: "active_v2"}})微调部分文档的分片键值,制造新的 key 边界 - 再用
sh.splitFind("db.col", {status: "active_v2"})定位新边界并切分,比硬猜splitAt值更可靠
查出 jumbo chunk 后,单文档大小到底多大才致命
关键阈值有两个,作用完全不同:
-
Object.bsonSize(doc) > 16777216(16MB):根本存不进 MongoDB,插入时就该报错。如果它已存在,说明早期用了--disableJavaScriptProtection或驱动绕过了校验,属于数据污染,得删或归档 2MB :这是迁移阶段的真实瓶颈。moveChunk 过程中要整块加载、序列化、发往目标分片,容易触发内存 OOM 或网络超时(默认 60 秒),日志里常见 <code>Location51008或Data transfer error- 别信“调大
chunkSize就能解决”——jumbo 是标记,不是尺寸开关。哪怕你设成 256MB,只要 chunk 里有单文档超限,它还是 jumbo
清理 jumbo 标记前必须做这三件事
sh.removeJumbo() 不是万能钥匙,乱用会导致元数据和实际数据不一致:
- 必须先确保该 chunk 已被成功 split 成多个小 chunk,且每个新 chunk 都不再含超大文档(用
find().sort({$natural: -1}).limit(1)逐个验证) - 必须停掉均衡器:
sh.stopBalancer(),否则 balancer 可能在你清理标记瞬间又触发一次失败迁移 - 清理命令必须带完整命名空间:
sh.removeJumbo("db.collection"),不能只写"collection",否则静默失败
最后提醒一句:jumbo chunk 本质是分片键设计缺陷的产物。查完、切完、清完,回头得重审分片键——比如把单一状态字段改成 {status: "active", created_at: ISODate(...)} 复合键,才能从根上避免。











