You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.29 07:46:47