Lorenz和Little:你的尾部延迟成本有多高?
摘要
探讨如何利用洛伦兹曲线量化每个延迟百分位数对平均延迟的贡献,并通过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$ 个百分位的延迟完成的请求。因为我们是基于监控和指标事后进行的,所以这并不重要。如果你试图用实时观察来做这件事,这个区别可能很重要。再次强调,业余统计学角。
相似文章
面向LLM赋能代理工作流的可靠设计:优化延迟-可靠性-成本权衡
本文分析了LLM赋能代理工作流中延迟、可靠性和成本之间的权衡,引入了性能模型,并推导出了如注水令牌分配等最优资源分配策略。
超越准确性与成本:面向动态工作负载的延迟感知LLM查询路由
一篇论文提出了一种延迟感知的LLM查询路由器,通过轻量级延迟估计器联合优化延迟、准确性和成本,在保持可比延迟的同时,准确率-成本效用提升高达40%。
认识爱丽丝。爱丽丝没耐心
这篇博文解释了系统延迟和恢复时间测量中的检查悖论,说明了为什么客户经历的平均等待时间比服务指标显示的要长。文中包含一个交互式模拟,并强调了理解分布尾部的重要性。
超越预测:面向尾延迟的LLM推理调度
本文提出了一种面向LLM推理的分布感知、无预测调度框架,利用轻量级统计信号以软优先级提升替代显式长度预测。该方法联合优化调度与缓存感知的抢占,以降低尾部延迟,相比具备完美长度知识的SRPT,P99 TTLT最多降低35-50%。
@h100envy: 前vLLM核心贡献者用34分钟解释如何将LLM推理成本降低10倍——比$3000的推理优化训练营更有效
一位前vLLM核心贡献者解释了如何通过LMCache将KV缓存卸载到CPU/SSD/远程存储,从而使LLM推理成本降低10倍,这一技术已被彭博等生产环境采用。