gridfs 存储小文件时开销远高于普通集合,1kb 文件实际落盘达4.2–8.3kb、放大4–8倍,且读写需两次查询、索引体积易超数据本身;推荐改用对象存储或本地文件系统。

GridFS 存储大量小文件(比如 1KB–10KB 的日志、图标、配置)时,开销远高于直接存入普通集合——不是“略高”,而是数倍乃至十倍级膨胀,且读写性能更差。根本原因不在“能不能存”,而在“每一步都多绕一圈、多占一倍空间”。
fs.files + fs.chunks 两层文档导致固定开销翻倍
哪怕一个 1KB 文件,GridFS 也必须写两条文档:一条 fs.files(元数据),一条 fs.chunks(实际数据)。实测 MongoDB 6.x + WiredTiger 下:
-
fs.files文档平均占 220–280 字节(含_id、filename、length、uploadDate等字段) -
fs.chunks文档即使只存 1KB 数据,因 WiredTiger 最小分配单元 + BSON 封装 + padding,磁盘占用常达 4–8KB - 也就是说:原始 1KB 数据 → 实际落盘约 4.2–8.3KB,放大 4–8 倍;而
fs.files开销已是原始数据的 200–280 倍
索引体积反超数据本身,尤其在百亿小文件场景
GridFS 默认为两个集合建强制索引:fs.files 上的 { uploadDate: 1 } 和 { filename: 1 },fs.chunks 上的 { files_id: 1, n: 1 }。这些索引不是“附赠”的:
- 每条
fs.files索引项都要存完整filename字符串(如"log_20260513_000001.txt"),长度动辄 20–40 字节;百亿文件下,仅这一索引就能突破 100GB -
fs.chunks.files_id索引才是内存杀手:千万文件 × 平均 50 片 = 5 亿索引项;若files_id用 UUID 或 URL 字符串代替默认ObjectId,单条索引内存涨 3–6 倍,WiredTiger 缓存迅速被挤爆 - 执行
mongofiles list会触发db.fs.files.find({}).sort({ uploadDate: -1 }),没索引就全表扫描;有索引也得加载海量索引页,拖慢整个实例
读取路径长、延迟高,不如内联存储直接
读一个 GridFS 小文件,MongoDB 必须:
- 先查
fs.files找到对应_id和chunkSize - 再按
files_id查fs.chunks拿出所有 chunk(哪怕只有一个) - 最后拼接二进制数据返回
- 而同样 1KB 文件存普通集合,只需一次
db.files.findOne({ _id: ... }),data字段直接是BinData,无额外查询、无拼接开销 - 实测:存 1 万个 8KB 文件,GridFS 总体积比内联存储大 3.2×,读取延迟高 2.1×
删除 fs.chunks.files_id 索引能减压,但治标不治本
确认不用直查 fs.chunks(比如不用 db.fs.chunks.find({ files_id: ... }) 做修复或监控),可安全删掉该索引:
-
db.fs.chunks.dropIndex({ files_id: 1, n: 1 })(注意复合索引名) - 删后 GridFS API(如
GridFSBucket.openDownloadStream())完全不受影响 - 但
fs.files的元数据开销、fs.chunks的块封装浪费、两次查询路径仍存在——索引只是压垮骆驼的最后一根稻草,不是根源
真正容易被忽略的是:你不是在选“数据库存文件”还是“文件系统存文件”,而是在选“要不要为每个小文件多付出 4–8KB 落盘成本 + 两次查询延迟 + 百亿级索引维护负担”。如果业务真有海量小文件,优先考虑对象存储(S3/MinIO)或本地文件系统 + 元数据分离管理,别让 GridFS 成为性能黑洞。











