应使用GridFSBucket.open_download_stream()配合分块读取(如read(65536))实现流式下载,避免read()一次性加载全量数据导致内存爆满;Web服务中通过StreamingResponse或Response生成器逐块yield,设置Content-Length和Accept-Ranges支持进度条与拖动播放。

GridFS下载大视频时内存爆满怎么办
直接用 gridfs.GridFSBucket.open_download_stream() 获取文件句柄但不流式读取,会把整个视频加载进内存——1GB视频可能吃掉2GB+ RAM。根本原因在于误用了 read() 一次性读全,而不是分块 read(chunk_size)。
- 务必调用
download_stream.read(1024*1024)(如每次1MB),而非download_stream.read() - 避免把整个
bytes存在变量里,边读边写入文件或转发给响应流 - 注意
download_stream是单次消费的,不能重复读或 seek
用PyMongo实现边下边写的真实流式下载
核心是拿到 GridOut 或流句柄后,用循环 + 小块读取 + 文件写入,绕过内存缓冲。适用于Web服务返回视频、CLI工具导出、或转存到本地磁盘。
- 先用
fs.find_one({"filename": "demo.mp4"})定位文件,检查length和chunkSize - 用
fs.open_download_stream(file_id)得到流,不要调.read() - 用
with open("out.mp4", "wb") as f:配合while True:+chunk = stream.read(8192)写入 - 读到空 bytes(
b"")即结束,记得stream.close()
stream = fs.open_download_stream(file_id)
with open("video.mp4", "wb") as f:
while True:
chunk = stream.read(65536) # 64KB per read
if not chunk:
break
f.write(chunk)
stream.close()
Web服务中直接流式响应(Flask/FastAPI)
用户请求视频时,你不该先存本地再 send_file;而是从GridFS读一块、yield一块,让WSGI/ASGI直接透传。否则多一倍IO且失去流控能力。
- FastAPI:返回
StreamingResponse,生成器每次yield chunk - Flask:用
Response(generate(), mimetype="video/mp4"),generate()函数内循环read()并yield - 必须设置
Content-Length(来自grid_out.length),否则浏览器无法显示进度条 - 别忘了设
Accept-Ranges: bytes,否则无法拖动播放
为什么不用 fs.get() 或 fs.get_last_version()
这两个方法返回的是完整 GridOut 对象,但底层仍会尝试把全部 chunks 拼成一个 BytesIO ——对大文件就是内存炸弹。它们适合小配置文件、图片缩略图等 KB 级数据。
立即学习“Python免费学习笔记(深入)”;
-
fs.get(file_id)内部调用open_download_stream后又.read(),白绕一圈 -
fs.get_last_version(filename)还要额外查索引,多一次 round-trip - 真正需要流式时,只认
open_download_stream+ 手动 chunk 读
GridFS本身不压缩、不分片传输,流式只是规避Python层内存堆积;如果视频太大或并发高,得考虑加Nginx缓存、用 mongofiles 命令行工具离线拉取,或者干脆换对象存储。


















