EC2服务器Node应用运行数小时后Puppeteer性能下降排查求助
Puppeteer+Bull+MongoDB应用运行数小时后性能衰减的排查思路
这个问题我太熟悉了,之前帮几个做任务处理的朋友排查过类似的性能衰减问题,结合你的技术栈,咱们从可能的原因到调试方法一步步捋:
可能的性能衰减原因
1. Puppeteer隐性资源泄漏(最常见)
虽然你说内存、CPU没异常,但Puppeteer很容易出现页面/浏览器上下文泄漏——比如任务完成后没调用page.close(),或者复用浏览器实例时没有清理旧的Incognito上下文。这些泄漏不会立刻拉高CPU/内存,但会占用Chromium内部的资源(比如渲染进程句柄、网络连接池),导致新任务启动页面、加载资源的速度越来越慢。
2. Bull队列任务元数据积压
Bull默认会把所有任务的状态(包括已完成的)存在MongoDB的jobs集合里。当处理7000个任务后,这个集合会累积大量历史数据,每次Bull拉取新任务时的查询开销会逐渐变大,间接拖慢任务处理的整体速度。
3. MongoDB查询/连接性能退化
- 随着任务结果不断写入MongoDB,结果集合的数据量越来越大,如果没有合适的索引,写入或后续查询的耗时会逐步上升;
- Node.js的MongoDB驱动连接池配置不合理(默认
poolSize=5),当并发任务较多时,会出现等待连接的情况,反映到事件循环延迟上升。
4. 事件循环隐性阻塞
你提到事件循环延迟从0.5ms升到1-2ms,说明随着运行时间增加,事件循环里的回调任务越来越多。比如:
- 代码中存在未清理的定时器(
setInterval)或事件监听器; - Puppeteer的某些异步操作没有正确收尾,导致回调堆积在事件循环队列里。
最佳调试方式
针对Puppeteer的调试
- 强制资源清理:在每个任务的
finally块里确保调用await page.close(),如果是复用浏览器实例,建议每处理10-20个任务就销毁并重建浏览器实例(或者用browser.createIncognitoBrowserContext(),用完销毁上下文); - 分步耗时统计:在任务代码中给Puppeteer的关键步骤加计时(比如启动页面、加载页面、执行脚本),打印每个步骤的耗时,看是哪一步开始变慢;
- 内存快照分析:用Chrome DevTools连接Node进程,抓取JS堆快照,查看是否有大量未被回收的
Page、BrowserContext对象。
针对Bull队列的调试
- 清理历史任务:检查MongoDB中
your-queue-name.jobs集合的文档数量,配置Bull的removeOnComplete选项自动清理已完成任务,比如:const queue = new Bull('your-queue-name', { redis: ..., // 你的配置 removeOnComplete: { count: 1000 }, // 只保留最近1000个完成的任务 removeOnFail: { count: 500 } }); - 监控队列吞吐量:用Bull的
queue.getJobs(['active', 'completed'])定期获取队列状态,或者启用Bull的内置UI(bull-board)实时查看任务处理的速度变化。
针对MongoDB的调试
- 开启慢查询日志:在MongoDB配置中设置
slowms: 100,记录所有耗时超过100ms的查询,看是否有写入/查询操作随着数据量增加变慢; - 检查索引:给结果集合的常用查询字段(比如任务ID、创建时间)添加索引,比如:
db.results.createIndex({ jobId: 1, createdAt: -1 }) - 调整连接池:在MongoDB驱动配置中增大
poolSize(比如设为10-20),同时监控连接池的使用情况,看是否有连接等待。
针对事件循环的调试
- 用Clinic.js分析:用
clinic bubbleprof启动应用,运行一段时间后生成事件循环分析报告,直观看到哪些操作占用了事件循环的时间; - 跟踪事件循环延迟:在代码中定期打印事件循环延迟:
结合日志找到延迟上升的时间段,对应排查当时的任务处理逻辑。setInterval(() => { const start = process.hrtime(); process.nextTick(() => { const diff = process.hrtime(start); const delay = (diff[0] * 1e9 + diff[1]) / 1e6; // 转换为ms console.log(`Event loop delay: ${delay}ms`); }); }, 1000);
额外建议
- 先测试小批量任务(比如1000个),看是否会出现同样的性能衰减,缩小排查范围;
- 调整Bull的并发数(比如
concurrency: 3),避免同时启动过多Puppeteer页面导致资源竞争; - 如果复用Puppeteer浏览器,不要长时间保持同一个实例,定期重启可以避免隐性资源泄漏。
内容的提问来源于stack exchange,提问作者ElmosGotAGun
相关产品推荐
相关产品推荐

