MongoDB 7.0 GridFS写入无显式文档锁,但真实瓶颈在于fs.files元数据争抢、fs.chunks批量插入引发的journal高频fsync,以及WiredTiger事务槽位(默认128)耗尽;db.currentOp()查不到锁等待,因chunks写入采用乐观重试而非排队阻塞。

MongoDB 7.0 的 GridFS 读写本身不加文档级锁,但并发瓶颈几乎全来自 fs.files 元数据争抢、fs.chunks 批量插入的 journal 刷盘压力,以及 WiredTiger 事务槽位耗尽——调优重点不在“锁”,而在绕过这三道硬关卡。
为什么 db.currentOp() 看不到 GridFS 写操作锁等待
GridFS 写入拆解为两步:先插入一条 fs.files 文档(带 _id),再批量插入多条 fs.chunks 文档(files_id 关联)。WiredTiger 对 fs.files 使用文档级锁,但对 fs.chunks 的批量写入默认走乐观并发控制,冲突时直接重试而非排队。所以你查 db.currentOp({ "waitingForLock": true }) 基本为空,真实阻塞藏在:
-
fs.files集合上高频insert或findOneAndUpdate(如覆盖同名文件)引发的 IX → X 锁升级争抢 - 每批
fs.chunks插入强制触发一次journalfsync,SSD 也扛不住 500+ QPS 的fsync调用 -
storage.wiredTiger.engineConfig.concurrentTransactions.write默认 128,小文件高并发上传时迅速打满,后续请求卡在事务分配阶段
chunkSizeBytes 设为 4MB 而不是默认 255KB 的真实原因
这不是“越大越好”,而是匹配磁盘 IO 模式和 OS 缓存效率:
- 机械盘场景下,255KB chunk 导致单个 10MB 文件触发约 40 次随机寻道;4MB chunk 将寻道次数压到 3 次以内,
iostat -x 1中await从 >30ms 降到 - SSD 上虽无寻道延迟,但 4MB chunk 让单次 write 更接近顺序 IO,提升 OS page cache 复用率,
unmodified pages evicted下降 60%+ - 必须显式传参创建 bucket:
new GridFSBucket(db, { chunkSizeBytes: 4 * 1024 * 1024 });已有数据无法在线修改,重传是唯一办法 - 注意:文件平均 db.fs.files.aggregate([{$group: {_id: null, avg: {$avg: "$length"}}}]) 先统计
三个必须同步调的 WiredTiger 参数(MongoDB 7.0 实测有效)
单独改 cacheSizeGB 对 GridFS 写入吞吐提升几乎为零,真正起效的是这三项组合:
-
storage.wiredTiger.engineConfig.cacheSizeGB: 24:32 核/64GB 服务器留 16GB 给 OS 缓存 journal 和 mmap 文件页,比 WT 缓存更直接影响写延迟 -
storage.wiredTiger.collectionConfig.blockCompressor: zstd:MongoDB 7.0+ 支持,比 snappy 压缩率高 20%,减少写入字节数和 journal 体积 -
storage.wiredTiger.engineConfig.journalCompressor: zlib:journal 必须用 zlib(zstd 不支持),降低日志 I/O 压力;禁用journal: false在生产环境等同于裸奔
监控时盯什么,而不是盯什么
别被 bytes currently in the cache 迷惑——GridFS chunk 流式读写绕过 WiredTiger page cache,这个值在高并发读时基本不动。重点关注:
-
tracked dirty bytes in the cache持续 >1GB:说明脏页积压,要么cacheSizeGB偏小,要么 journal 配置没跟上写入节奏 -
pages evicted by age显著高于pages evicted by the application:页面老化太激进,chunk 数据还没被读就淘汰,缓存应优先分配给fs.files索引 -
db.serverStatus().metrics.lock.acquireWaitCount在fs.files上突增:确认是元数据锁瓶颈,此时应检查是否在高频覆盖同名文件或未建索引
最易被忽略的一点:GridFS 的并发上限其实由客户端驱动决定。PyMongo 默认 maxPoolSize=100,Node.js driver 默认连接池更小,不调大连接池,再好的服务端调优也白搭。


















