·更新于 2026年10月25日· 22 次浏览 Node.jsMongoDBRedis工程实践BUG解决
周报导出拖垮 MongoDB:我用流式游标和 Redis 队列改成异步下载
一次周报导出接口慢查询和内存飙升,我把同步大查询改成异步任务:Redis 队列管状态,MongoDB 流式游标分批读,最后生成临时下载链接。

作者
ZHOUYI
上周运营导周报,点一下导出,接口转圈两分钟,MongoDB 慢查询告警,Node 进程内存也飙上去。原因很直接:一次性 find 几十万条文档,再在内存里拼 CSV。同步导出这种活,不能挂在 HTTP 请求里硬扛。
把导出改成异步任务
我改成点击导出先创建任务:写入任务表或 Redis,状态设为 queued,立刻返回 taskId。前端轮询任务状态,不再等待长连接。Worker 从 Redis 队列取任务,按时间范围和用户维度分批查询。这样接口只负责下单,查询压力被挪到后台,还能限制并发。
流式游标分批读
javascript
const cursor = db.collection('weekly_events')
.find({ createdAt: { $gte: start, $lte: end } })
.sort({ _id: 1 })
.batchSize(1000);
let buffer = [];
for await (const doc of cursor) {
buffer.push(doc);
if (buffer.length >= 1000) {
await appendCsv(buffer);
buffer.length = 0;
}
}
if (buffer.length) await appendCsv(buffer);关键是不要 toArray。用 cursor 逐条读,攒满一批就追加写文件,写完清空数组。每批之间还能让出事件循环,避免 CPU 一直占满。查询条件最好走索引,并按 _id 或时间排序,避免深分页。
状态、重试和下载链接要闭环
- 任务状态至少包含 queued、running、success、failed,前端按状态展示进度。
- 失败重试要带次数上限和退避时间,重试时重新分批,不能把半成品文件当成成功。
- 成功后生成临时下载链接或对象存储签名 URL,过期后重新生成,别把本地磁盘路径直接返回。
“踩坑点:任务状态别只放内存,Node 重启就丢了;下载文件最好带任务 ID 命名,方便排障和清理。若用 Redis 做锁或队列,也要考虑过期和异常释放。”
改完后,导出接口基本秒回,MongoDB 的瞬时压力变成平稳的小批量读取。对后台报表这类需求,异步任务、流式读取、可追踪状态,比继续优化单次大查询更靠谱。