一个数据库团队花了三周搭建的压测环境,在模拟10万QPS流量时,P99延迟稳定在50毫秒以内。上线后第一天的流量峰值刚到6万QPS,监控系统就开始疯狂报警——大量请求超时,用户反馈页面卡顿。

这不是个例。2013年,Azul Systems的CTO Gil Tene在Strange Loop会议上发表了名为《How NOT to Measure Latency》的演讲,揭示了一个困扰行业多年的真相:大多数负载测试工具都在系统性地说谎

协调遗漏:测量系统与被测系统的"共谋"

Gil Tene提出了一个术语来描述这个问题的本质:协调遗漏(Coordinated Omission)

想象一个客服中心的场景。每个客服代表(测试线程)在处理完一个客户请求后,会等待一段时间再拨打下一个电话。当系统响应变慢时,客服代表会被迫等待,但他们不会在系统变慢时额外拨打更多电话来"弥补"。

这正是传统负载测试工具的工作方式。当被测系统响应变慢时,测试工具会自动降低请求速率——因为测试线程被阻塞了。这种"协调"导致了一个致命后果:测试工具避开了测量那些本该发生的延迟峰值

Red Hat性能团队在他们的技术博客中举了一个数学例子:假设一个系统在测试期间发生了10分钟的完全中断。在传统测试模式下,只有10个测试线程会被阻塞10分钟。但在真实世界中,如果请求以每分钟20个的速率持续到达,这10分钟内会产生200个请求,它们都要排队等待。

如果只统计那些被阻塞的测试线程,平均响应时间会被严重低估。更糟糕的是,测试工具根本无法察觉自己遗漏了什么

开放模型与封闭模型:性能差异可达一个数量级

2006年,卡内基梅隆大学的研究团队发表了一篇题为《Closed versus open system models and their impact on performance and scheduling》的论文,系统性地比较了两种系统模型。

封闭系统模型假设存在固定数量的用户,每个用户在前一个请求完成后才会发起新请求。这是大多数传统负载测试工具的设计基础。

开放系统模型则假设请求独立于系统状态到达,不受之前请求完成情况的影响。这更接近真实世界的Web服务场景——用户不会因为服务器慢就减少访问。

论文中的实验数据显示了一个令人震惊的事实:在相同负载下,开放系统的平均响应时间可以比封闭系统高出一个数量级以上

论文中定义了"服务需求变异性"($C^2$,变异系数的平方)这个关键参数。当$C^2$较高时(比如$C^2 = 43$,代表某些超级计算场景),即使是MPL=1000的封闭系统,其响应时间仍然远低于开放系统。

更重要的是,调度策略的效果在两种模型下截然不同。在开放系统中,短作业优先(SJF)等策略可以带来数量级的性能提升;但在封闭系统中,调度策略的收益微乎其微。研究者发现,当思考时间为零时,所有工作保持型调度策略在封闭系统中的性能完全相同——这是Little定律的直接推论。

百分位数的幻象:平均值的陷阱依然存在

很多人已经意识到平均值的问题,转而使用百分位数。但前Netflix架构师Adrian Cockcroft在2023年的文章中指出了一个更深层的陷阱:百分位数无法合并

如果你测量了60个一分钟区间的P99值,你无法通过平均这些值得到一小时的P99。因为每一分钟的P99可能对应完全不同的请求。Cockcroft提出了一个更激进的方案:使用混合模型分解响应时间分布

通过期望最大化(EM)算法,可以将响应时间分布分解为多个正态分布的叠加。每个分布对应系统中不同的延迟来源——数据库查询、缓存命中、网络传输等。这种方法不仅能提供比百分位数更丰富的信息,还允许数学上正确的合并和预测。

他在文章中展示了一个真实Web服务的响应时间分布,分解后显示了至少8个不同的峰值。这些峰值的相对高度和位置,能够反映系统内部状态的变化——缓存命中率波动、数据库连接池状态、GC停顿等。

工具设计的结构性缺陷

ScyllaDB团队在他们的技术分析中详细剖析了YCSB基准测试工具的设计问题。YCSB默认工作在封闭模式下:一个线程发送请求,等待响应,然后发送下一个。

当研究者尝试修复这个问题时,他们发现需要同时处理两个层面:

负载生成层面:测试工具必须能够独立于系统响应维持请求发送速率。这需要区分"静态调度"(基于预设时间点)和"动态调度"(基于上一次请求完成时间)。前者在遇到延迟峰值时会排队补发,后者则会自然降低速率。

延迟测量层面:当请求被延迟发送时,记录的延迟数据必须进行修正。正确的延迟计算应该是:

$$\text{Latency} = (\text{now} - \text{intended\_time}) + \text{service\_time}$$

其中intended_time是请求本该发送的时间点,而不是实际发送时间。

ScyllaDB的实验显示,在未修正的情况下,YCSB报告的P99延迟可能只有几百微秒;但修正后,同一测试的真实P99延迟可能高达600毫秒以上——差了三个数量级

检测与规避:工程实践指南

Red Hat团队提供了一个简单但有效的检测方法:CTRL+Z测试

在负载测试运行时,暂停被测服务进程(kill -STOP),等待一段时间后恢复(kill -CONT)。如果测试工具报告的延迟数据没有显示明显的峰值,那么它就存在协调遗漏问题。

对于需要正确测量的场景,有几个关键原则:

  1. 明确区分系统模型:根据真实工作负载选择开放、封闭或部分开放模型。Cockcroft提出的经验法则是:每会话请求数少于5个时,开放模型更准确;超过10个时,封闭模型更接近现实。

  2. 使用正确的工具配置:JMeter等传统工具需要显式设置吞吐量目标,而非仅设置并发用户数。k6等新一代工具提供了arrival-rate执行器,能够以开放模型方式工作。

  3. 验证测量有效性:对比"预期发送请求数"和"实际发送请求数"。如果两者存在显著差异,说明负载生成器已被阻塞。

  4. 采用分布分析而非百分位数:收集原始延迟数据或高精度直方图(如HdrHistogram),进行分布分解分析。

尾声

负载测试的困境反映了一个更深层的工程问题:当测量系统与被测系统耦合时,测量结果会系统性偏离真相

这不是一个可以通过简单调参解决的问题。它要求测试工程师理解排队论的基本原理,区分不同的系统模型,选择合适的工具配置,并对结果进行批判性审视。

那些看起来"过于完美"的压测报告,往往是最危险的。它们可能正在掩盖那些会在生产环境中让系统崩溃的延迟峰值。


参考文献

  1. Gil Tene, “How NOT to Measure Latency,” Strange Loop Conference, 2015
  2. Bianca Schroeder et al., “Closed versus open system models and their impact on performance and scheduling,” NSDI ‘06, 2006
  3. ScyllaDB Team, “On Coordinated Omission,” ScyllaDB Blog, 2021
  4. Red Hat Performance Team, “Coordinated Omission,” Red Hat Blog, 2022
  5. Adrian Cockcroft, “Percentiles don’t work: Analyzing the distribution of response times for web services,” Medium, 2023
  6. Gil Tene, “Correcting YCSB’s Coordinated Omission problem,” psy-lob-saw blog, 2015
  7. Grafana Labs, “Open source load testing tool review,” Grafana Blog, 2020