gridfs 不能直接分片,需手动对 fs.files 和 fs.chunks 分别分片且策略不同:fs.files 应用高基数字段(如 {uploadid: "hashed"}),fs.chunks 必须用 {files_id: 1} 且类型严格匹配 fs.files._id,禁用哈希分片。

GridFS 本身不能直接分片,fs.files 和 fs.chunks 集合必须分别处理
很多人误以为 GridFS 是个“可分片的存储抽象”,其实它只是客户端驱动封装的一套约定:底层仍是两个普通集合 fs.files 和 fs.chunks。MongoDB 不允许对 GridFS 自动分片——你得手动对这两个集合分别启用分片,且**必须用不同策略**。
常见错误是给 fs.files 和 fs.chunks 都设同一个分片键(比如都用 filename),结果导致所有 chunk 写入都绑定到同一组 files 文档所在的分片,反而放大热点。
-
fs.files应该用高基数、写入分散的字段作分片键,例如{ uploadId: "hashed" }或{ userId: 1, timestamp: 1 } -
fs.chunks必须用files_id字段(即对应fs.files._id)作为分片键,且类型必须严格匹配;如果fs.files._id是ObjectId,那fs.chunks.files_id就不能是字符串或数字 - 绝不能对
fs.chunks使用哈希分片键:因为每个 chunk 必须和它的 parent file 落在同一个分片上,否则mongos无法保证原子性读取,查询会失败或返回损坏文件
为什么 fs.chunks 分片键必须是 files_id,且不能哈希
GridFS 协议要求:一个文件的所有 chunk 必须能被一次定位并顺序读取。如果 fs.chunks 按 { files_id: "hashed" } 分片,那同一个 files_id 的多个 chunk 可能散落在不同分片,mongos 在执行 find({ files_id: ObjectId("...") }) 时无法确定目标分片,只能广播查询——这不仅慢,还破坏了 GridFS 的语义保证。
实测中,一旦对 fs.chunks 启用哈希分片,GridFSBucket.openDownloadStream() 会间歇性超时,mongostat 显示 qr 队列飙升,而各分片 query 数值却很低,典型路由层卡顿。
- 确认
fs.chunks.files_id类型与fs.files._id一致:用db.fs.chunks.findOne().files_id和db.fs.files.findOne()._id对比 - 分片命令必须是
sh.shardCollection("fs.chunks", { files_id: 1 }),不能加"hashed" - 如果已有数据且
files_id类型混乱,先用updateMany统一类型,再分片;否则shardCollection会报KeyPattern does not match existing index
fs.files 分片键选错,照样引发写入热点
fs.files 是 GridFS 的元数据入口,所有上传都先插入一条 fs.files 文档。如果它用了单调递增字段(如时间戳、自增 ID)作分片键,新上传就会持续打到同一个分片,后续所有关联的 fs.chunks 也跟着集中过去——哪怕 fs.chunks 分片正确,整体写入还是热的。
更隐蔽的问题是:很多业务用 filename 作分片键,但用户上传文件名重复率极高(如 report.pdf),导致低基数 + 哈希后碰撞严重,chunk 实际仍集中在 1–2 个分片。
- 优先选天然高基数字段:如
uploadId(UUID)、userId+timestamp复合键 - 避免
filename、contentType、status等低基数字段 - 若必须用时间相关字段,加盐处理:写入前拼接随机后缀,如
new Date().getTime() + "_" + Math.random().toString(36).substr(2, 5) - 分片后立刻运行
sh.status(),检查fs.files的 chunk 分布是否均匀;若某分片只有 1–2 个 chunk,说明分片键失效
真正容易被忽略的是:fs.files 和 fs.chunks 的索引不匹配会拖垮整个写入链路
分片只是第一步。如果 fs.files 上没建以分片键开头的索引,mongos 无法快速定位元数据,上传时会先全量扫描或广播;如果 fs.chunks 缺少 { files_id: 1, n: 1 } 复合索引,按顺序读 chunk 时就会触发 COLLSCAN,连带拖慢写入确认速度。
尤其注意 MongoDB 6.0+ 对分片集合建索引的限制:background: true 在 mongos 上无效,且必须前台构建;如果索引字段含数组,而分片键又不是该数组字段,创建会直接失败。
- 确保
fs.files有{ uploadId: 1 }或{ userId: 1, timestamp: 1 }索引(按分片键顺序) - 确保
fs.chunks有{ files_id: 1, n: 1 }索引:这是 GridFS 读取顺序的关键,缺失会导致每次下载都扫全表 - 不要在
fs.files分片键上建稀疏索引(sparse: true):分片路由依赖该字段存在,稀疏索引会让部分文档“不可见” - 用
db.fs.files.explain("executionStats").find({ uploadId: "xxx" })验证是否走索引,totalDocsExamined应等于 1











