高频修改文件应避免GridFS,改用普通文档存BinData并$set更新;超16MB需自定义分块,主文档存元信息和chunkRefs数组,各chunk为独立文档。

高频修改文件触发全量重写,不是慢,是模型错配
GridFS 每次“更新”同名文件,本质是删旧 + 传新两步非事务操作。哪怕只改 1 字节,uploadFromStream 也会:生成新 fs.files 文档、把整个文件按 chunkSizeBytes 拆块写满 fs.chunks、再异步清理旧数据。这导致三重开销:写入量翻倍、索引重建压力大、WiredTiger B-tree 频繁分裂。
直接用普通文档 + $set 更新替代 GridFS
如果文件实际大小稳定在几 MB 以内(比如配置模板、渲染产物、轻量报告),别碰 GridFS。把二进制内容存进 BinData(0, "...") 字段,配合 $set 原子更新:
- 读写都是一次文档操作,无元数据/块分离问题
- 加复合索引如
{ filename: 1, version: -1 }支持按版本查最新 - 高频更新下磁盘 IO 和锁竞争远低于 GridFS 的 chunk 批量写入
- 注意:BSON 限制仍是 16MB,超了就得切分逻辑(见下一条)
超 16MB 且需局部更新?自己分块,不走 GridFS Bucket
GridFS 的 chunk 抽象不可控,而你的业务可能只需要追加或按时间范围更新。参考真实生产方案:
- 主文档只存元信息和
chunkRefs: [ObjectId]数组 - 每个 chunk 是独立文档(如
reportChunks集合),含periodStart和data字段 - 写入时判断:新增数据
- 查询最新数据 = 主文档 +
$lookup最近 N 个 chunk,天然支持断点续查
这种模式绕开了 GridFS 的上传路径限制,也规避了 deleteOne + openUploadStream 的窗口期风险。
真要保留 GridFS 兼容性?至少把 chunkSizeBytes 设为 1MB
默认 256KB 在高频场景下会把一个 5MB 文件拆成 20 个 chunk,每次更新就是 20 次插入 + 索引更新。设为 1MB 后,同样文件只占 5 个 chunk,IO 次数压低 75%。但这只是止痛,不是根治——只要还在用 uploadFromStream 覆盖同名文件,就逃不开写放大和孤儿块问题。
真正难的不是换代码,而是让所有调用方接受:高频修改 ≠ 文件覆盖,而是版本发布 + 生命周期管理。任何试图用 GridFS 模拟“编辑”行为的设计,都会在并发、中断、回滚上反复出事。











