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

Heroku压测无法复现H12响应超时错误问题咨询

问题描述
  • 此前在Heroku服务器上遭遇H12响应超时错误,故障发生时预估在线并发用户仅20余人。
  • 原计划通过压力测试复现故障,后续调整服务器配置避免同类问题复发,但无论对Heroku实例施加多高访问压力,始终无法复现H12错误。
  • 本次测试使用puppeteer-cluster模拟250个并发用户访问,测试代码如下:
const { Cluster } = require('puppeteer-cluster');

var workerID = Math.floor(Math.random() * 10000);

(async () => {
    // 初始化集群
    const cluster = await Cluster.launch({
        concurrency: Cluster.CONCURRENCY_CONTEXT,
        maxConcurrency: 5,
        puppeteerOptions: {
          headless: true, 
          args:['--no-sandbox', '--disable-setuid-sandbox']
        }
    });

    // 定义页面访问、截图任务
    await cluster.task(async ({ page, data: url }) => {
        await page.goto(url, {
          waitUntil: 'networkidle2', timeout: 0
        });
        await page.waitForTimeout(10 * 1000) 
        const path = url.replace(/[^a-zA-Z]/g, '_') + '.png';
        await page.screenshot({ path });
        console.log(`${workerID}: Screenshot of ${url} saved: ${path}`);
    });

    // 向队列添加待访问页面
    for(var i=0; i<100; i++){
      cluster.queue('https://server.com/page1');    
      cluster.queue('https://server.com/page2');
      cluster.queue('https://server.com/page3');  
    }

    // 所有任务完成后关闭集群
    await cluster.idle();
    await cluster.close();
})();
  • 测试配置逻辑:单实例模拟5个并发用户加载页面,共部署50个dynos,理论可实现250个并发用户的模拟效果。
  • 测试结果:完全未触发H12错误,反而出现大量H27错误。
  • 核心疑问:为何上述压力测试配置无法复现H12响应超时错误?
    测试截图
原因分析

复现失败的核心原因是完全没搞懂H12和H27两类错误的触发逻辑差异,当前压测场景从根上就不满足H12的触发条件,反而精准命中了H27的触发场景:

  • 两类错误的本质区别
    • H12(请求超时)的触发前提是:请求已经成功被Heroku路由层转发到你的应用dyno,但dyno在30秒内没有返回任何HTTP响应字节给路由层。这类错误的根因100%在应用侧:比如数据库慢查询锁死、后端接口死锁、Node.js单线程事件循环被长耗时CPU任务完全阻塞、外部依赖调用超时未设置兜底逻辑,导致请求到达dyno后,dyno完全没有资源处理请求、无法返回响应,卡满30秒后路由层就会抛出H12。
    • H27(客户端请求中断)的触发前提是:客户端主动断开了和Heroku路由层的连接,此时路由层还没等到后端dyno的完整响应。这类错误的根因在客户端侧,和后端应用是否超时没有关系。
  • 压测触发H27而非H12的具体原因
    1. 压测路径完全没命中故障触发点
      第一次出现H12时仅有20个并发用户就能触发故障,本质是当时的用户请求刚好命中了特定的异常逻辑——比如某条带未加索引慢查询的列表页、某个触发批量同步任务的操作、某个携带特殊参数会触发死循环的接口,而不是随便访问几个页面就能把应用卡爆。当前压测只固定访问三个页面,如果这三个页面本身没有触发那个卡死bug的逻辑,哪怕堆到1000并发也碰不到H12。
    2. 压测客户端先于服务端到达性能瓶颈
      用puppeteer做压测,每个并发都是一个完整的浏览器上下文,内存和CPU开销极高:50个dyno硬跑250个Chrome实例,首先崩溃的是跑压测脚本的dyno,不是要测的目标服务。压测侧的Chrome实例要么因为内存不足崩溃、要么因为本地资源不够处理页面渲染主动断开连接,路由层收到客户端断连信号就会直接记录H27——这些请求根本没等到后端dyno卡满30秒就被客户端掐断了,自然不可能触发H12。
    3. 压测的资源水位没达到故障阈值
      第一次出H12时,大概率伴随dyno CPU跑满100%、内存占满触发磁盘swap、数据库连接池被打空这类异常水位。当前压测如果因为客户端提前断连,根本没把后端dyno的CPU、内存、连接数堆到当时故障的水位线,自然复现不了超时问题。
复现建议
  • 先拉取第一次H12故障时段的全量日志,定位当时用户集中访问的路径、请求参数、触发的后台动作,不要无差别压测三个固定页面。
  • 不要用puppeteer堆并发做基础压测,换wrk、vegeta这类轻量HTTP压测工具发纯协议请求,把客户端侧开销降到最低,先验证单接口在高并发下的响应耗时,确认是否能达到30秒以上的超时阈值。
  • 如果确实需要模拟真实浏览器加载行为,先把puppeteer的并发数降到压测集群能稳定承载的阈值,不要硬堆250个Chrome实例;同时去掉timeout:0的配置,给页面访问加上明确的超时监听,记录每个请求的实际耗时和断连原因,先排除压测侧自身断连的干扰。
  • 压测过程中实时监控dyno的CPU、内存、数据库连接数指标,确保压测时的资源水位和第一次故障时的水位对齐,否则不可能复现问题。

内容的提问来源于stack exchange,提问作者mkto

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 12:31:01