chunk size 过小会导致频繁分裂、迁移激增、空洞堆积和磁盘碎片化;默认64mb已平衡性能与均衡性,生产环境应保持该值,仅在明确存在长期超128mb大chunk时可上调至128mb。

Chunk Size 设置过小会导致频繁分裂和空洞堆积
默认 chunkSize 是 64MB,但很多团队在高写入场景下主动调小(比如设成 16MB 或更小),以为能提升负载均衡灵敏度。结果反而引发连锁问题:分裂太勤,moveChunk 操作激增,而迁移失败或中断时容易留下 orphaned document;同时每个新 chunk 初始化会预分配空间,大量小 chunk 叠加导致数据文件碎片化加剧,物理磁盘上出现难以回收的“空洞文件”。
关键判断依据是看 sh.status() 输出中 chunks 数量是否远超实际数据量级——例如一个仅存 20GB 数据的集合却有 5000+ chunks,基本可确认 chunk 过碎。
- 生产环境建议保持默认
chunkSize: 64,除非你明确观察到单个 chunk 长期 >128MB 且持续拖慢查询或迁移 - 若真需调整,只允许向上调(如 128MB),禁止向下调;调整命令必须在
mongos执行:sh.setBalancerState(false)→sh.splitAt("db.coll", {"_id": ObjectId("...")})配合人工干预,而非全局改配置 - 修改后必须等 balancer 完成一轮完整扫描(可通过
sh.getBalancerStatus()查看activeWindow和stopped状态)再恢复
分片键选择不当会放大空洞影响
空洞本身不直接影响查询性能,但当分片键是低基数字段(如 status、type)或单调递增字段(如 timestamp)时,新写入全部挤在少数 chunk 上,旧 chunk 却因历史数据删除而长期闲置——这些“冷 chunk”里的空洞无法被新数据复用,最终变成磁盘上的稳定垃圾空间。
典型症状是 db.collection.stats() 显示 size 很小但 storageSize 异常大,且 avgObjSize 明显低于预期。
- 优先选高基数、分布均匀、非单调的字段作分片键,例如
user_id(哈希分片)或复合键{region: 1, _id: 1} - 绝对避免用
Date类型字段单独做范围分片键,尤其在 TTL 删除高频的场景下 - 上线前用真实流量压测分片分布:
sh.status()中各 shard 的chunks数量偏差不应超过 ±15%
balancer 策略没配好会让空洞“卡住”不清理
balancer 不只是负责搬数据,它还决定哪些 chunk 该合并(merge)。但默认情况下,MongoDB 不会自动合并小 chunk,即使它们总大小远低于 chunkSize。这就导致大量
注意:compact 在分片集群中只能对单个 shard 上的 collection 执行,且要求该 shard 当前是 primary —— 如果 balancer 正在迁移,或者目标 shard 处于 recovering 状态,命令直接报错 not master。
- 启用自动合并需手动开启隐藏参数(3.6+):
db.adminCommand({setFeatureCompatibilityVersion: "3.6"})后,在 config server 上执行:db.settings.update({_id: "balancer"}, {$set: {enableAutoMerge: true}}, {upsert: true}) - 定期检查孤儿文档:
db.runCommand({cleanupOrphaned: "db.coll"}),这个命令不阻塞读写,但只清理已确认 orphaned 的文档,不能替代 chunk 合并 - 如果发现某 shard 存储量明显偏高且
sh.status()显示大量 compact,最后重启 balancer
真正要收缩空间,得从副本集层面动手
所有线上 compact 或 balancer 调整,都只是整理逻辑层碎片;物理磁盘空间能否释放,取决于底层存储引擎(WiredTiger)是否能把空洞对应的数据块归还给操作系统。而 WiredTiger 默认只在 checkpoint 时尝试释放,且受限于 cache_size 和脏页比例。
所以当你看到 storageSize 居高不下,但 dataSize 已经很小,说明空洞已经固化在磁盘文件里了——这时候唯一可靠的办法,是让某个 secondary 节点重同步,强制重建整个数据文件。
- 操作前确保有足够磁盘空间容纳两份数据(旧文件 + 新同步数据)
- 重同步期间,该节点的 oplog 必须能覆盖整个同步窗口,否则会触发 initial sync,耗时更长
- 不要在只有两个副本的集群里对 primary 直接删数据目录——降权后那个节点变 secondary,但一旦它开始 initial sync,集群就只剩一个可写节点,存在单点风险











