GridFS 本身不会直接导致 OOM,根本原因是流控失效致数据在V8堆内存堆积;上传前OOM因同步读文件(如readFileSync),应改用fs.createReadStream直连uploadStream;下载时OOM因下游消费慢,需设readableHighWaterMark、用pipe并监听close/error销毁流;并发场景须协同控制读取、管道缓冲与流终结,漏调end()/destroy()将致内存持续上涨。

GridFS 本身不会直接导致 OOM,OOM 出现在 Node.js 进程里,根本原因是流没控住——数据在进 MongoDB 前或出 MongoDB 后,在 V8 堆内存里堆积成山。
uploadStream 写入前就 OOM:文件没流式读取
常见错误现象:RangeError: Invalid array buffer length、Node 进程 RSS 内存瞬间飙到 2GB+、上传卡死无报错
本质是上游把整个文件同步加载进内存,uploadStream.write() 还没开始,V8 就撑不住了。比如用 fs.readFileSync()、Buffer.from(arrayBuffer)、Express 的 bodyParser 自动解析 multipart,全都会触发这个问题。
- 必须用
fs.createReadStream(path, { highWaterMark: 64 * 1024 })直连uploadStream,禁用任何中间Buffer拼接 - Web 端上传时,别用
FileReader.readAsArrayBuffer()读全文件;改用fetch()+ReadableStream直传(现代浏览器支持) - 后端接收 multipart 时,用
busboy或formidable流式解析,立刻.pipe(uploadStream),不缓存 body - 上传前检查
req.headers['content-length'],超限(如 >500MB)直接res.status(413).end()拒绝,别让它进 Node
openDownloadStream 读取时 OOM:下游消费跟不上
常见错误现象:小文件正常,视频类大文件一下载 Node 内存就暴涨;stream.on('data', ...) 回调里不做 pause()/resume() 控制;stream.pipe(res) 后客户端断连,流没销毁
openDownloadStream() 返回的是可读流,但它默认会预取多个 chunk 到内存,尤其当 MongoDB 响应快、网络慢、下游写入阻塞时,背压信号失效,数据就堆在 V8 堆里。
- 显式设
stream.readableHighWaterMark = 2 * 1024 * 1024(2MB),匹配视频 GOP 大小,避免频繁 read() 调用 - 优先用
stream.pipe(res),它自动处理背压,但前提是res不被意外中断(需监听'close'和'error'并调stream.destroy()) - 不要用
stream.on('data', callback)手动拼 Buffer,除非你严格控制pause()/resume()节奏 - 对超大视频,考虑用
openDownloadStream(fileId, { start: 0, end: 1024 * 1024 })做范围读,避免全量加载
并发上传/下载压垮内存:管道没协同限速
常见错误现象:Promise.all([uploads]) 导致所有上传同时触发 stream 管道;pipeline() 里只设了 highWaterMark,但下游 fs.WriteStream 没监听 drain;MongoDB cursor 滞留不释放
GridFS 的背压不是单点配置能解决的,得三层一起控:读取节奏、管道缓冲、流终结。
- 并发上传改用
Promise.allSettled(),避免一个失败拖垮全部,也防止单次触发过多 stream 实例 - 用
stream.pipeline()时,必须传{ highWaterMark: 64 * 1024 },这是管道内部 buffer 上限 - 下游写入目标(如
fs.createWriteStream)必须支持write()返回布尔值,并监听'drain'事件做节流 - 务必监听
pipeline的'error'和'close',出错时手动调downloadStream.destroy(),否则 MongoDB cursor 会长期滞留
真正容易被忽略的是:OOM 往往不是某一行代码写的错,而是流生命周期管理断在了某个环节——uploadStream.end() 漏掉、destroy() 忘调、pipe() 后没兜底监听 close。这些地方不显眼,但一漏就是内存持续上涨,直到进程被系统 kill。











