GridFS 不支持预读,openDownloadStream() 按需顺序拉取 chunk;chunkSize 设为4MB可减少查询与寻道,需配合fs.chunks哈希分片、Redis缓存热点chunk及HTTP层缓存优化。

GridFS 本身不支持预读,openDownloadStream() 按需顺序拉取 chunk
你无法在 GridFS 协议层开启“预读”——它不是存储引擎,不控制 I/O 调度,也不维护 read-ahead 缓存。每次 openDownloadStream() 调用,驱动都只构造一个 fs.chunks 游标,从 n: 0 开始逐个 find(),直到 n 达到文件总块数。没有 lookahead、不跳序、不推测访问模式。
常见错误现象:
- 试图在应用层提前
toArray()所有 chunk → 触发多次网络往返 + 多次磁盘寻道,小文件反而更慢 - 用
cursor.skip()或cursor.limit()模拟“跳转” → 驱动仍要扫描跳过的 chunk 文档,WiredTiger 不加速这种随机跳 - 以为增大
cacheSizeGB能缓存 chunk 流 → 实测db.serverStatus().wiredTiger.cache["bytes currently in the cache"]在 GridFS 读期间几乎不涨
fs.chunks 的页面缓存命中率由 chunkSize 和磁盘 I/O 模式决定
WiredTiger 的 page cache 只缓存 B-tree 页面(索引、_id、小文档),而 fs.chunks.data 是大 BinData 字段,走的是 mmap 文件页路径,实际由 OS 文件系统缓存(page cache)接管。真正影响“是否从内存返回 chunk”的,是 OS 是否还留着该 chunk 对应的磁盘页。
关键实操建议:
- 把
chunkSizeBytes设为4 * 1024 * 1024(4MB):让小文件只占 1 个 chunk,减少查询次数和磁盘寻道;已有数据必须重传,files.chunkSize字段不可改 - 避免默认
255KB:10MB 图片会拆成 40+ chunk,机械硬盘上等于 40+ 次寻道,OS page cache 很难全留住 - 确认
fs.chunks.files_id有索引:db.fs.chunks.createIndex({ files_id: 1 }),否则单 chunk 查询可能全表扫 - SSD 用户可忽略此条;机械硬盘用户务必关掉
journal写延迟(storage.journal.enabled: false)仅限测试环境
高频 chunk 应该缓存在 Redis,而不是 MongoDB 或 OS 层
想提升“重复读同一 chunk”的速度,唯一高效路径是绕过 MongoDB 查询链路,把已解包的 data 字段直存 Redis。缓存 fs.files 元数据毫无意义——它太小、变更少、且与 fs.chunks 不同步。
正确缓存粒度:
- key 设计为
gridfs:chunk:{files_id}:{n},例如gridfs:chunk:60a1b2c3d4e5f67890abcdef:0 - value 存原始
Buffer(不要 JSON 序列化),过期时间按业务设(如 1 小时) - 读取逻辑:先查 Redis,未命中再调
bucket.openDownloadStream(),拿到后异步回填 Redis - 不要缓存整文件:视频首帧、PDF 前 3 页、音频前 30 秒才是真实热点,缓存全部 chunk 是资源浪费
分片集群下 fs.chunks 必须用 files_id 哈希分片
如果你用了分片集群但没手动分片 fs.chunks,所有 chunk 都落在主分片,openDownloadStream() 会退化成单点查询,吞吐卡死。这不是缓存问题,是路由瓶颈。
必须执行的操作:
- 对
fs.chunks启用哈希分片:sh.shardCollection("mydb.fs.chunks", { files_id: "hashed" }) - 确保
fs.files已按业务字段分片(如{ user_id: 1 }),且files_id与之共置(即user_id和files_id来自同一批写入) - 验证方式:
db.fs.chunks.find({ files_id: ObjectId("...") }).explain("executionStats")中nShards应为 1;若大于 1,说明 chunk 分散了,性能必跌 - 别碰
mongos的readPreference:GridFS 读必须走primary,设成secondary会导致 chunk 顺序错乱
最常被忽略的一点:GridFS 的“缓存”不在数据库里,而在 HTTP 层和客户端。哪怕你把 chunk 全塞进 Redis,如果 Nginx 没配 proxy_cache_key 和 Cache-Control,浏览器照样每刷一次就重拉一遍。ChunkSize 是前置条件,HTTP 缓存策略才是最终生效环节。

















