平均值毫无意义

Lobsters Hottest 新闻

摘要

一篇博客文章,说明仅依赖平均值来评估性能提升可能会产生误导,通过使用合成延迟数据,展示了查看完整分布(通过百分位数、密度图和CDF)的重要性。

<p><a href="https://lobste.rs/s/p0fdmk/mean_means_nothing">评论</a></p>
查看原文
查看缓存全文

缓存时间: 2026/07/28 22:30

# 均值毫无意义 来源:https://fzakaria.com/2026/07/27/the-mean-means-nothing 最近我试图验证与 `lld` 在 `$DAYJOB` 相关的性能优化,但看到基准测试有改进,而生产环境的实时仪表盘却没有明显变化,这让我有些沮丧。 由于之前一直从事 Web 服务方面的工作,我习惯查看单个时间序列仪表盘(有时会看几个百分位数),原本期望能看到一些显著变化,但数据噪音太大,无法得出结论。 结果发现,一位同事在评估构建速度优化时也遇到了类似问题。影响构建的因素很多:冷缓存、增量构建、本地构建、远程构建等,构建时间会因系统状态和工作负载的不同而差异巨大。她最终利用累积分布函数(CDF)来可视化数据,这让我深受启发。 这促使我探索了除 CDF 之外的几种不同数据可视化方式,并认识到单张图表或单个统计量往往不足以讲述完整的故事。本文将用一个*合成*数据集,展示不同可视化方式如何就同一数据讲述不同的故事。目的是说服你*审视*数据,而不仅仅用单个数字来概括。 以下所有内容均来自一个固定种子的合成数据集。完整的脚本可以在 [这个 gist](https://gist.github.com/fzakaria/17c72f0eddc0f10469e008e67e1385cc) 中找到。它是一个带有 `nix-shell` shebang 的单个文件,只要你使用 [nix](https://nixos.org/),就可以精确复现每一个图表。 > **注意** 我借助 AI 生成本文中的数据和图表以辅助叙述。如果这让你不爽,抱歉。🤷 ### § [“更糟了”的发布](https://fzakaria.com/2026/07/27/the-mean-means-nothing#the-rollout-that-made-it-worse) 设定是这样的:我们运营一个典型的 Web 服务,计划在一周内逐步推出一个新的缓存层,以期降低请求延迟。 变更已完全部署,显示**均值**的延迟仪表盘如下图所示: 发布前后均值延迟的柱状图;发布后为 122 毫秒,比发布前的 112 毫秒高出约 9% 均值延迟*上升*了,从 112 毫秒增加到 122 毫秒。☹️ 于是我们拉了一个 SEV,回滚变更,开始写事后复盘。对吧?🤔 ### § [一个数字,四个故事](https://fzakaria.com/2026/07/27/the-mean-means-nothing#one-number-four-stories) 通常,特别是对于 Web 服务,查看不同的百分位数(尤其是尾部的 p95 和 p99)是一个好习惯。 现在我们面临一个问题:*每个人都是对的*。均值说明变更带来了轻微退化。中位数(p50)说明变更是重大胜利,典型请求的速度**几乎翻倍**。p99 说明这是一次 SEV,最差的请求延迟增加了一倍多。 基于同一组数据计算出的均值和中位数,指向了*相反的方向*。 工程师们常被教导要数据驱动,但人们很容易挑选支持自己论点的统计量。 ### § [看形状](https://fzakaria.com/2026/07/27/the-mean-means-nothing#look-at-the-shape) 接下来我们可以做的是绘制分布的形状。以下是变更前后两种延迟的密度图: 变更前后延迟的密度图;变更前是一个单峰,变更后有一个高而快的峰值,外加一个位于慢速区域的第二峰 就是这样。🤓☝️ “变更前”是**一个整齐的峰**。“变更后”是**两个峰**。 这已经解释了之前的矛盾,但要正确可视化它有点棘手。形状取决于我们选择的平滑参数,两个填充在重叠处相互干扰,并且很难直接从中读出*百分位数*。我可以看到有两个群体;但我很难看出中位数去了哪里。 ### § [你还没用过的最佳图表](https://fzakaria.com/2026/07/27/the-mean-means-nothing#the-best-chart-youre-not-using) 累积分布函数(CDF)可以同时回答每个百分位数的一个问题:**有多少比例的请求的延迟在 *x* 毫秒或以下?** 变更前后延迟的 CDF;两条阶梯曲线在大约 140 毫秒处交叉 CDF 是一种非常直观的方式,可以在单个图表中可视化多个百分位数。根据曲线形态,我们可以了解请求延迟在整个群体中的分布情况。 我发现将“变更前”和“变更后”的 CDF 绘制在同一张图上进行比较非常有用。这样你可以可视化不同百分位数的偏移,理解变更如何影响整个群体。 在我们的故事中,对于低于 140 毫秒的请求延迟,“变更后”曲线向左移动了。这意味着更多请求比以前完成得更快。在 140 毫秒以上,“变更后”曲线高于“变更前”曲线,这意味着更多请求比以前完成得更慢。两条曲线在大约 140 毫秒处交叉,这是变更从胜利转为失败的转折点。 > **提示** 两条交叉的 CDF 是*没有任何单个百分位数能够总结*的变更的明确标志,因为效果的正负取决于你询问的是哪个百分位数。 ### § [谁赢了,赢了多少](https://fzakaria.com/2026/07/27/the-mean-means-nothing#who-won-and-by-how-much) CDF 告诉我们*效果会改变符号*:变快或变慢。接下来的问题显然是*变化了多少*,在分布的每个点上。我们可以绘制每个百分位数 *p* 的变更后延迟减去变更前延迟,这称为**移位函数**。 移位函数:每个百分位数的延迟变化;在 p76 左右之前为负,之后急剧上升进入尾部 零线以下表示变更后更快;零线以上更慢。我们可以可视化每个百分位数上变化的幅度。 ### § [退化从一开始就存在](https://fzakaria.com/2026/07/27/the-mean-means-nothing#the-regression-was-there-all-along) 到目前为止,我们只看过两个冻结的快照:变更前和变更后。但发布通常不是瞬间完成的。在这个故事中,我们花了一周时间逐步推出新的缓存层,从 0% 流量逐步增加到 100%。 那么*每一天*是什么样子的?将每天一个分布堆叠起来,就得到了**山脊线图**: 每个发布日延迟的山脊线图(对数轴);随着发布从 0% 逐步增加到 100%,第二个峰值逐渐出现 现在我们可以可视化退化随时间显现的过程。我们可以看到主峰(快速请求)随着发布的推进向左滑动,而第二个峰(慢速请求)在右侧出现。中位数在下降,但慢速请求的数量和延迟在悄悄增长。 这里的 x 轴是对数轴。延迟大致呈对数正态分布,在线性轴上,快速峰就像一个高大的尖峰旁边有一个看不见的涂抹;对数轴使得两个峰都能被识别为峰。 类似地,我们可以将其压缩到单个网格中,作为**热图**。每一列代表一天,颜色表示每个延迟水平的流量占比: 按发布日划分的延迟密度热图;一个深色主带在下降,同时另一个带在发布推进过程中出现在更高的位置 你可以隐约看出一个新群体在微弱地出现。 任何在整个一周内计算的聚合值都会将这七个截然不同的日子混合成一个模糊的数字,完全隐藏了趋势。 ### § [双峰是有原因的](https://fzakaria.com/2026/07/27/the-mean-means-nothing#the-bimodality-had-a-cause) 我们现在已经彻底确定了*发生了什么*。下一个问题是*为什么*。这实际上与 `$DAYJOB` 的情况非常相似,当时我不得不按二进制大小(即 >50MiB)对数据进行切片,以观察延迟分布中的双峰性。 在我们的故事中,新的缓存层要么从缓存中服务请求(**命中**),要么通过额外的网络跳转回退到后端(**未命中**)。我们可以根据这个属性拆分“变更后”的请求,并为每个类别绘制一条 CDF: 按缓存命中/未命中拆分后的变更后延迟 CDF,基线用虚线表示;两个清晰单峰曲线位于其两侧 按缓存结果划分后,每个群体再次变为单峰。 我们可以清楚地看到,缓存**命中**比旧的基线更快,因为它们向左移动了。 缓存**未命中**则因为额外的网络跳转而付出了代价,位于右侧很远的位置。 ### § [为什么是*那些*请求?](https://fzakaria.com/2026/07/27/the-mean-means-nothing#why-those-requests) “某些请求未命中缓存”是一个机制,但还不是原因。*哪些*请求未命中,以及为什么未命中? 每个请求还携带一个我尚未使用的字段:**响应大小**。 缓存倾向于存储小的、热门的对象;大的对象要么被逐出,要么根本放不进去。我们可以绘制延迟与响应大小的关系图,并根据缓存命中/未命中为每个点着色。我们还可以在每个坐标轴上添加密度图,以查看两个群体沿每个轴的分布情况:**联合图**: 变更后流量中延迟与响应大小的联合图,按缓存结果着色;缓存命中聚集在小且快的区域,缓存未命中聚集在大且慢的区域,边缘密度显示了每个轴上的分叉 我们可以看到两个清晰的群体集群:小且快(缓存命中)和大且慢(缓存未命中)。很明显,延迟分布中的双峰性是由响应大小分布中的双峰性引起的。 现在我们有了可操作的信息:提高缓存的最大对象大小,或者拆分大的响应。🔥 ### § [一图胜千数](https://fzakaria.com/2026/07/27/the-mean-means-nothing#a-graph-is-worth-a-thousand-numbers) 通常,单个面板或图表最多只能讲述部分故事。更糟糕的是,它可能具有误导性。拥有同一数据的多个视角有助于理解完整的故事。 我尤其印象深刻的是,CDF 如何能在单个图表中传达整个分布,特别是在比较可能看起来是多个群体的情况时。 > “不要相信任何你没有亲手伪造的统计数据。” – 温斯顿·丘吉尔

相似文章

我们应该摒弃平均CPU利用率

Hacker News Top

本文解释为何平均CPU利用率对于延迟敏感型工作负载是一个误导性指标,利用排队论和一个真实的生产事故案例,主张采用更细致的监控方法。