Lorenz和Little:你的尾部延迟成本有多高?

Lobsters Hottest 工具

摘要

探讨如何利用洛伦兹曲线量化每个延迟百分位数对平均延迟的贡献,并通过Little定律将其与并发性联系起来,证明尾部延迟通常主导成本和容量。

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

缓存时间: 2026/07/30 01:45

# 洛伦兹与小定理:你的尾部成本有多高? 来源:https://brooker.co.za/blog/2026/07/29/lorenz-and-little.html 洛伦兹和小定理听起来像是2015年的某个潮人汉堡店。 欢迎来到马克的业余统计学角!今天的话题:为什么我在优化成本时非常关注尾延迟。 我之前写过关于尾延迟对客户体验重要性的文章(例如,2026年 (https://brooker.co.za/blog/2026/06/19/waiting.html),2021年 (https://brooker.co.za/blog/2021/10/20/simulation.html),2021年 (https://brooker.co.za/blog/2021/04/19/latency.html),以及2017年 (https://brooker.co.za/blog/2017/12/28/mean.html))。今天,我想从成本和容量的角度来谈谈尾延迟。 和许多系统运维人员一样,我使用百分位数来思考尾延迟。 这里有一个问题:我的每个延迟百分位数对平均延迟的贡献有多大?直观上,答案是“相当大”,但能否量化?可以!我们寻找的东西是经验洛伦兹曲线 (https://en.wikipedia.org/wiki/Lorenz_curve)。它直接回答了这个问题:给定一个延迟百分位数 $P$(例如 p99=100ms),比 $P$ 短的请求对平均延迟的贡献是多少?(我们称之为 $L(P)$,那么问题的实际答案是 $1 - L(P)$。) 从延迟样本开始,计算相当简单:`L = sum(sorted(x)[:k]) / sum(x)`(对于一组 `n` 个延迟样本 `x`,`k = p * n`)。从分位数向量开始,事情会稍微复杂一些,因为我们必须选择如何在样本之间进行插值以及如何外推到最大值。这里我使用幂定律进行插值,这有点罪过¹ (https://brooker.co.za/blog/2026/07/29/lorenz-and-little.html#foot1),但对于我们的目的来说已经足够好了。 ``` # Calculate 1 - L(p) for a vector of measured quantiles # q - an array of quantiles (e.g. [1, 10, 200, 10000, 20000]) # p - an array of percentiles they're measured at (e.g. [0, 0.5, 0.9, 0.99, 0.999]) # OneMinusL - One minus the empirical Lorenz curve for each of the percentiles def OneMinusL(q, p): q, p = np.asarray(q, float), np.asarray(p, float) assert q.shape == p.shape and q.size >= 2, "q, p 必须长度相同且至少为2" assert p[0] == 0 and p[-1] < 1, "p 必须从0开始(否则会丢失质量),且结束于1以下" assert np.all(np.diff(p) > 0), "p 必须严格递增" assert q[0] > 0 and np.all(np.diff(q) > 0), "q 必须为正且严格递增" w = q*(1-p) a = np.log((1-p[1:])/(1-p[:-1]))/np.log(q[:-1]/q[1:]) assert np.all(np.abs(a-1) > 1e-9), "某些单元格alpha == 1:除以零" assert a[-1] > 1, f"尾部alpha={a[-1]:.3f} <= 1:均值无穷,无法估计" c = np.r_[0, np.cumsum(a/(a-1)*(w[:-1]-w[1:]))] return 1 - c/(c[-1] + a[-1]/(a[-1]-1)*w[-1]) ``` 例如: ``` print(OneMinusL([1, 10, 200, 10000, 20000], [0, 0.5, 0.9, 0.99, 0.999])) [1. 0.99377318 0.93082759 0.51712644 0.10342529] ``` 这表明,对于这个分布,等于或长于中位数(p50)的请求贡献了约99%的平均延迟,而等于或长于p99的请求贡献了约52%的平均延迟² (https://brooker.co.za/blog/2026/07/29/lorenz-and-little.html#foot2)。 这很有趣,但我为什么关心?因为利特尔定律 (https://en.wikipedia.org/wiki/Little%27s_law)告诉我们,这个值(对平均值的贡献)同时也是系统并发度的贡献。在一个简单的线程系统中,如果 $1 - L(p) = k$,那么我们的服务中有 $100k$% 的繁忙线程正在处理延迟³ (https://brooker.co.za/blog/2026/07/29/lorenz-and-little.html#foot3)高于第 $p$ 个百分位的请求。队列、基于事件的实现等会使并发度与成本的映射复杂化,因此你需要结合自身系统的上下文来思考。 根据我的经验,在服务中 $1 - L(0.99)$ 大于 $0.5$ 甚至 $0.75$ 的情况并不少见。这说明优化尾部可能对减少并发度有远超预期的贡献,进而减少容量需求、锁竞争以及高并发带来的其他问题。尾部的服务成本往往不成比例地高,因此在我们考虑优化时,它们的重要性也尤其突出。我们不应犯下剪掉它们的错误,因为通常正是它们在驱动成本!(当然,还有糟糕的客户体验。) 如果你想用一些数值进行试验,在这里输入你的百分位数,看看你的曲线: p0: ms p50: ms p90: ms p99: ms p99.9: ms 来自等于或高于每个百分位数的请求的平均延迟占比:p50**–**,p90**–**,p99**–**,以及 p99.9**–**。根据利特尔定律,这些也是它们在系统并发度中的份额。 **脚注** 1. 这里有两个罪过:一个是任意选择插值方式,另一个是任意选择外推方式。你的百分位数越密集,且数据越符合帕累托分布,这个问题的影响就越小。但是如果你想得到精确的经验答案,你需要使用不同的方法(具体来说,基于排序原始样本的方法)。我说了是业余统计学角。 2. 另一个罪过当然是,我们测量的百分位数是对真实百分位数的估计,并且随着百分位数的增大,不确定性也在增加。如果你有重尾和小样本,可能会得到非常多变的结果。不理想,但不会改变结论。 3. 更准确地说,是那些将以高于第 $p$ 个百分位的延迟完成的请求。因为我们是基于监控和指标事后进行的,所以这并不重要。如果你试图用实时观察来做这件事,这个区别可能很重要。再次强调,业余统计学角。

相似文章

认识爱丽丝。爱丽丝没耐心

Lobsters Hottest

这篇博文解释了系统延迟和恢复时间测量中的检查悖论,说明了为什么客户经历的平均等待时间比服务指标显示的要长。文中包含一个交互式模拟,并强调了理解分布尾部的重要性。

超越预测:面向尾延迟的LLM推理调度

arXiv cs.LG

本文提出了一种面向LLM推理的分布感知、无预测调度框架,利用轻量级统计信号以软优先级提升替代显式长度预测。该方法联合优化调度与缓存感知的抢占,以降低尾部延迟,相比具备完美长度知识的SRPT,P99 TTLT最多降低35-50%。