根本原因是fs.chunks分片键设计不合理导致jumbo chunk堆积,平衡器拒绝迁移;正确方案是用{files_id: 1, n: 1}复合分片键或高基数业务字段,且须提前配置、禁用平衡器后手动迁移。

GridFS 在分片集群中迁移慢,根本原因不是 GridFS 本身,而是 fs.chunks 和 fs.files 集合的分片策略 + 平衡器行为被隐式限制。5.0 版本没改这个逻辑,只是底层索引扫描快了——但若 chunk 分布不均、分片键不合理或平衡器被卡住,再快的引擎也动不了。
为什么 balance 停在 fs.chunks 上不动
分片集群里,fs.chunks 是真正存二进制块的集合,它必须被分片;而 fs.files 通常不建议分片(除非文件元数据查询极重),否则会破坏文件读取的局部性。但很多人直接对整个 GridFS 桶执行 sh.enableSharding("mydb") + sh.shardCollection("mydb.fs.chunks", {"files_id": 1}),却忽略了两个致命点:
-
files_id字段本身基数极低——所有 chunk 都指向同一个文件,files_id值重复成千上万次,导致 chunk 无法分裂,最终形成大量 Jumbo Chunk - 平衡器默认只迁移 可分裂的 chunk;一旦 chunk 被标记为 jumbo,
moveChunk直接拒绝,balancer就跳过它,看起来就是“卡住” - 查证方式:
sh.status()里看fs.chunks的 chunk 分布,再执行db.fs.chunks.find({files_id: ObjectId("...")}).count()看单个文件 chunk 数是否远超 255KB 默认大小(比如一个 2GB 文件生成 8000+ chunk)
fs.chunks 分片键不能只用 files_id
单独用 {files_id: 1} 当分片键,等于把每个文件的所有 chunk 全钉死在一个分片上,完全违背分片初衷。真实可用的方案只有两种:
- 用复合分片键:
{files_id: 1, n: 1}——n是 chunk 序号,天然高基数,能确保同一文件的 chunk 散开;但要求你提前在fs.chunks上建好该分片键,并且mongos版本 ≥ 5.0(旧版本不支持n字段自动填充) - 换更合适的业务字段:如果文件有明确归属(如
tenant_id、user_id),且该字段基数够高(>10k),优先用{tenant_id: 1, files_id: 1},兼顾路由效率与分布均匀性 - 切忌用
_id或时间字段——fs.chunks._id是 ObjectId,前4字节是时间戳,写入仍会热点
手动触发迁移前必须关掉 balancer
平衡器和 moveChunk 不能共存。如果你看到 moveChunk 报错 The collection's metadata lock is already taken,八成是 balancer 正在后台跑迁移任务,抢了锁。正确顺序是:
- 先停:
sh.stopBalancer(),等sh.getBalancerState()返回false - 再确认无进行中迁移:
db.adminCommand({ "balancerStatus": 1 })查inBalancerRound和activeMigrations - 手动迁 chunk 时,务必加
_secondaryThrottle: true参数,避免源分片压力突增:db.adminCommand({moveChunk: "mydb.fs.chunks", find: {files_id: ..., n: 123}, to: "shard02", _secondaryThrottle: true}) - 迁完几批后,用
sh.startBalancer()恢复,别忘了设平衡窗口避开业务高峰
5.0 的索引优化只对查询生效,不加速迁移
别被“MongoDB 5.0 GridFS 更快”误导。5.0 对 fs.chunks 的 {files_id: 1, n: 1} 索引扫描确实更快,但这只影响「下载时按序拉 chunk」的速度,跟「chunk 迁移」无关。迁移走的是内部 chunk 移动协议,不走用户级查询路径。所以:
- 即使你建了
{files_id: 1, n: 1}索引,迁移慢仍是分片键设计或 jumbo chunk 导致的 - 检查
db.fs.chunks.getIndexes()输出里有没有{files_id: 1, n: 1}—— 没有就补:db.fs.chunks.createIndex({"files_id": 1, "n": 1}) - 迁移过程中,
db.currentOp({ns: "mydb.fs.chunks"})如果显示大量moveChunk操作卡在secs_running > 300,基本可断定是目标分片磁盘或网络慢,不是索引问题
最常被忽略的一点:GridFS 分片不是“打开开关就行”,它要求你从第一天就决定好 fs.chunks 的分片键,并接受它长期不可变。临时想靠 balance 去“救火”,往往只是把问题从一个分片转移到另一个分片而已。











