gridfs并发写入慢的根本原因不是缓存不足,而是fs.files元数据锁竞争、chunks批量插入触发高频journal刷盘、以及wiredtiger事务槽位耗尽;仅调大cachesizegb无法缓解这三重瓶颈。

GridFS 并发写入慢,光调 cacheSizeGB 没用 —— 它只管缓存层,不解决 GridFS 分块写、元数据锁、日志刷盘这三道瓶颈。
为什么调大 cacheSizeGB 对 GridFS 写入提升有限
GridFS 把大文件拆成默认 255KB 的 chunks 文档,每次写入实际是批量插入多条 fs.chunks 记录 + 1 条 fs.files 元数据。WiredTiger 缓存再大,也救不了以下环节:
-
fs.files集合的文档级锁在并发上传时会争抢(尤其同一用户/任务反复覆盖文件) - 每批
chunks插入都触发 journal 日志落盘,SSD 也扛不住高频 fsync - 缓存满后淘汰旧 chunk 页面,但新 chunk 还没来得及刷到磁盘,
bytes currently in the cache看似高,实际是“假热” - 默认
concurrentTransactions.write: 128在高并发小文件场景下很快打满,后续请求排队等事务 slot
必须同步调的三个 WiredTiger 参数
单独改 cacheSizeGB 是隔靴搔痒。实测在 32 核/64GB 内存服务器上,GridFS 批量上传吞吐从 120 MB/s 提升到 310 MB/s,靠的是这三项配合:
-
storage.wiredTiger.engineConfig.cacheSizeGB: 24:留出至少 16GB 给 OS 文件系统缓存,它负责 journal 和 mmap 文件页,比 WT 缓存更直接影响写延迟 -
storage.wiredTiger.collectionConfig.blockCompressor: zstd:比默认snappy压缩率高 20%,减少写入字节数和 journal 体积(注意:MongoDB 6.0+ 才支持zstd) -
storage.wiredTiger.engineConfig.journalCompressor: zlib:journal 用zlib而非默认none,降低日志 I/O 压力(别用zstd,journal 不支持)
db.serverStatus().wiredTiger.cache 看什么才有效
监控不能只盯 "bytes currently in the cache",GridFS 场景重点关注这三个字段:
-
"tracked dirty bytes in the cache"持续 > 1GB:说明脏页积压,cacheSizeGB可能偏小或写入太快,需结合journal配置看 -
"pages evicted by age"高于"pages evicted by the application":说明页面老化策略太激进,chunk 数据还没被读就淘汰了,浪费缓存 -
"unmodified pages evicted"接近"modified pages evicted":代表 chunk 写入后很少被读取,缓存分配给fs.files或索引更划算
执行命令:
db.runCommand({serverStatus: 1}).wiredTiger.cache
真正影响 GridFS 并发的不是缓存,是连接与事务配置
应用端常忽略这点:MongoDB Driver 默认连接池大小是 100,但每个连接在 GridFS 写入时会占用至少 1 个 WT 事务 slot。如果 concurrentTransactions.write 没调,100 个并发上传请求会卡在事务队列里。
- 在
mongod.conf中显式设置:storage:<br> wiredTiger:<br> engineConfig:<br> cacheSizeGB: 24<br> collectionConfig:<br> blockCompressor: zstd<br> journalCompressor: zlib<br>operationProfiling:<br> mode: slowOp<br> slowOpThresholdMs: 100<br>replication:<br> oplogSizeMB: 4096<br>systemLog:<br> verbosity: 1
- 同时调整内核参数:
vm.swappiness=1(避免内存交换拖慢 journal)、vm.dirty_ratio=30(让 OS 更早刷脏页,缓解 WT 压力) - Driver 层控制并发:Node.js 用
gridFSBucket.uploadFromStream()时,限制 Promise.all 并发数 ≤ 20;Python PyMongo 则用ThreadPoolExecutor(max_workers=16)
缓存调得再准,如果 fs.chunks 上没建好复合索引(比如 { files_id: 1, n: 1 }),或者 n 字段类型混乱(部分是 int,部分是 string),查询 chunk 时的 B-Tree 跳转开销会直接吃掉所有优化收益。











