gridfs默认不缓存,因其仅为存储抽象层,不处理http响应头;必须在流开始前手动设置cache-control等响应头,否则无效,且需确保查询有索引、缓存键稳定(如用md5作_id)才能真正提升命中率。

直接在 GridFS 下载响应里设 Cache-Control 就行,但必须在流开始前设置,否则无效;MongoDB 本身不加任何缓存头,全靠你手动控制。
为什么 GridFS 默认不缓存?
GridFS 是存储抽象层,不是 HTTP 服务。它只管把 chunks 拼成流吐出去,openDownloadStream() 返回的 Readable 流压根不碰 HTTP 头。浏览器每次请求都会触发完整读取 + 解包 + 传输,和 CDN、Nginx 完全无关。
- 别指望
cacheSizeGB配置能加速图片读取——它只影响 WiredTiger 页缓存,对流式大文件基本没用 -
fs.files查询本身如果没索引(比如按filename查),也会成为瓶颈,缓存再好也卡在这一步 - 前端用
filename请求,后端却用_id查,中间没做映射缓存 → 每次都查fs.files表
怎么正确设置 Cache-Control 响应头?
必须在调用 pipe() 或 stream.on('data', ...) 之前,用 res.set() 或 res.setHeader() 写死头信息。顺序错了,Node.js 会报 “Cannot set headers after they are sent”。
- 小图(如头像、图标)建议设
Cache-Control: public, max-age=31536000(一年) - 带版本号或哈希的文件名(如
logo.a1b2c3.png)可放心设长缓存;含查询参数的(如avatar.jpg?ts=123)会被视为不同资源,缓存失效 - 务必同时设
Content-Type,GridFS 不自动推断,从file.contentType取最稳
示例(Express):
app.get('/files/:id', async (req, res) => {
const fileId = new ObjectId(req.params.id);
const downloadStream = bucket.openDownloadStream(fileId);
const file = await db.collection('fs.files').findOne({ _id: fileId });
if (!file) return res.status(404).send('Not found');
res.set({
'Content-Type': file.contentType || 'application/octet-stream',
'Cache-Control': 'public, max-age=31536000',
'ETag': `"${file._id}"`, // 可选,配合 If-None-Match
});
downloadStream.pipe(res); // 必须在这之后
});
用 Nginx 代理时要注意什么?
Nginx 的 proxy_cache 不认 GridFS,只认上游响应里的 Cache-Control 和 Expires。如果后端没设,Nginx 默认不缓存。
- 确保 Nginx 配置里开了
proxy_cache_valid 200 1h;这类规则,且proxy_cache_use_stale合理兜底 - 避免在
location里用正则匹配/files/.*\?.*—— 查询参数会让 key 变复杂,容易击穿 - 如果业务用自定义 bucket 名(如
uploads),Nginx-gridfs 模块需显式配置gridfs_database和gridfs_bucket,默认只认fs
真正容易被忽略的是:缓存键稳定性。用 ObjectId 当 _id 时,哪怕内容一样,每次上传都是新 ID;用 md5(fileContent) 当 _id 才能天然复用缓存。否则你调优了半天 Nginx,实际缓存命中率还是零。











