卷 III · 那面墙CH 10深度 10/24

最后 10% 的产能,比前面 90% 都贵

同一条曲线,换个方向读,就变成了一张价目表。而这张价目表有一个非常干净的结论,干净到让人怀疑是不是算错了——但它是精确的,而且它同时是好消息和最坏的消息。

★ 价目表一条恒等式对称的噩梦

▷ 先估一个数

你的服务跑在 90% 的利用率上,延迟已经很难看了。你想把平均等待减半

假设流量不变、代码不变、请求分布不变,唯一的手段是加机器。

你需要把产能提高多少?

A 大约 10% B 大约 50%——等待减半,产能大概也要提这么多 C 翻倍。要让等待减半,直觉上就该翻倍 D 不可能。在 90% 这个位置,加多少机器都救不回来

一条恒等式

把「等待减半」这个要求写成方程,解出来:

# 现在:等待正比于 ρ/(1−ρ)
# 目标:等待变成一半,也就是 ρ'/(1−ρ') = ½ × ρ/(1−ρ)
# 流量不变,所以 ρ' = ρ × (原产能 ÷ 新产能)

# 解出来(推导略,两行代数):
新产能 ÷ 原产能 = 2 − ρ

需要增加的产能 = (2 − ρ) − 1 = 1 − ρ

# ★ 也就是说:
#   需要增加的产能,**恰好等于你现在的余量**

填几个数看看:

现在的 ρ现在的余量等待减半要加多少产能加完之后的 ρ
50%50%+50.0%33.3%
70%30%+30.0%53.8%
80%20%+20.0%66.7%
90%10%+10.0%81.8%
95%5%+5.0%90.5%
99%1%+1.0%98.0%

第二列和第三列完全相同。这不是我把表编成这样的——这是那条恒等式。

◆ 好消息:越靠墙,买产能越划算

这和大多数人的直觉正好相反。人们通常觉得「已经这么满了,加点机器杯水车薪」。

事实恰恰相反:在 99% 的地方,加 1% 的机器就能让等待减半。在 50% 的地方,你得加 50% 才能做到同样的事。

原因很简单:等待反比于余量。在余量只剩 1% 的时候,加 1% 的产能就是把余量翻倍。你花的是产能的百分之一,买到的是余量的一倍。

推广一下:要把等待压到原来的 1/h,需要增加的产能是当前余量的 (h−1) 倍。ρ=99% 时想让等待降到十分之一,只需要加 9% 的产能。

坏消息:同一句话反过来读

这条恒等式是对称的。它说「靠墙时一点产能能换巨大的改善」,也就同时说了「靠墙时一点产能损失就是灾难」。

现在的 ρ掉 10% 产能之后等待变成几倍
50%ρ → 55.6%1.25 倍
70%ρ → 77.8%1.50 倍
80%ρ → 88.9%2.00 倍
90%ρ → 100.0%∞(撑不住了)
95%ρ → 105.6%∞(撑不住了)

在 90% 的位置上掉一台十分之一的产能——比如十台机器挂了一台,或者一次发布让单请求耗时涨了 11%——系统直接从「有点慢」跳到「撑不住」。

没有中间状态。没有「稍微慢一点」。ρ 一旦触到 1,队列就进入无限增长,而且第 21 章会告诉你,它欠下的债要花远超故障时长的时间才能还清。

◆ 这就是「雪崩」的机制

线上事故的经典剧本是这样的:

  1. 系统平时跑在 88% 左右,一切正常,监控是绿的;
  2. 一台机器挂了(或者一次发布让处理慢了 15%);
  3. ρ 越过 1,队列开始线性增长;
  4. 延迟涨 → 客户端超时 → 重试 → 负载又涨 → ρ 更大;
  5. 五分钟后,整个系统完全不可用。

第 2 步只是「掉了 10% 的产能」。而从第 2 步到第 5 步,中间没有任何一个可以停下来喘口气的稳定状态——因为那条曲线在 ρ=1 处是竖直的。

这也是为什么「留余量」不是保守,是结构性的必要:余量是这个系统里唯一的负反馈来源。

怎么用这张价目表

三个具体的用法。

1. 把「加机器」的讨论从拍脑袋变成算术。「我们要加多少机器」这个问题,通常的答案是「先加 50% 看看」。有了这张表,可以改成:现在 ρ 是多少?想让延迟改善几倍?那么要加的产能就是 当前余量 × (倍数 − 1)

2. 定容量的时候,先定余量,再定机器数。常见做法是「按峰值 QPS 配机器,再乘个 1.5 的系数」。更好的做法是反过来:先决定「我要留多少余量」(这直接等价于「我能忍多久」,通过第 9 章那条 ρ★ 公式),再倒推机器数。

3. 算「能挂几台」。这是最实用的一条。你有 N 台机器跑在 ρ 上,挂掉 k 台之后利用率变成 ρ × N/(N−k)。要求它小于 1,得到:

能挂的台数 k < N × (1 − ρ)

# ρ=0.9,10 台   →  k < 1.0    ★ 一台都不能挂
# ρ=0.9,100 台  →  k < 10     可以挂 9 台
# ρ=0.7,10 台   →  k < 3      可以挂 2 台
# ρ=0.5,10 台   →  k < 5      可以挂 4 台

# 注意这只是"不崩"的条件。要"不明显变慢",
# 得把 ρ 代进第 9 章那条 ρ★ 公式里,能挂的还要更少。

这个式子解释了一件常被误解的事:N+1 冗余在高利用率下是不够的。「我们有 10 台,挂 1 台还有 9 台,能撑」——如果平时跑在 90%,挂一台之后 ρ 精确等于 1,你不是「还能撑」,你是刚好站在墙上

∑ 算一遍:边际价格

还有一个角度:再往上挤一点点,边际代价是多少?对那条曲线求导:

d/dρ [ ρ/(1−ρ) ]  =  1 ÷ (1−ρ)²

# 边际价格(每多挤 1 个百分点,等待多几个服务时长)
ρ = 50%   →  1/(0.5)²  = 4.00      ÷100 = 每挤 1% 多等 0.04
ρ = 90%   →  1/(0.1)²  = 100.00    ÷100 = 每挤 1% 多等 1.00
ρ = 99%   →  1/(0.01)² = 10000.00  ÷100 = 每挤 1% 多等 100.00

# 边际价格按 **余量的平方** 反比增长。
# 从 90% 挤到 91%,代价是从 50% 挤到 51% 的 25 倍。

注意这里出现了平方。上面那张「减半价目表」是线性的(加的产能等于余量),但边际代价是平方的。两者不矛盾:前者问的是「达到某个目标要花多少」,后者问的是「再走一小步的斜率」。

实践上的含义:不存在「稍微再挤一点」这种操作。在墙脚下,每一小步的代价都是上一步的好几倍。

✎ 术语正名:「成本优化」

基础设施领域的「成本优化」几乎总是指提高利用率:合并实例、超卖、装箱、降配。

这些动作本身没问题,但它们的账通常只算一半:省下的机器钱是显性的、月底就能看见;换来的延迟劣化和脆弱性是隐性的,可能三个月后以一次事故的形式出现。

这一章给了你把另一半账算出来的工具。「把利用率从 70% 提到 85%」在数学上等价于「把平均等待变成 2.4 倍,并且把能挂的机器数从 3 台降到 1 台」。这句话应该和省下的钱写在同一张幻灯片上。

▸ 在现实里

为什么大厂的机器看起来那么闲。公开的资料里,很多互联网公司的服务器平均 CPU 利用率长期在 20%–40%。这经常被外界当成浪费。但把第 16 章的扇出效应也算进去之后,你会发现:对一个需要保证 p99 的扇出系统来说,单机 30% 就是那条曲线和容忍线的交点。

航空公司的座位。航班的上座率可以逼近 100%,因为「座位」不是队列——没坐上的人改签,不会在跑道上排队。但登机口、跑道、行李分拣是队列,它们的利用率就必须留余量。同一家公司的不同资源,最优利用率可以差三倍,取决于它是不是一条队。

为什么「双十一」要提前一个月扩容。不是因为流量涨了多少倍(那部分好算),而是因为在墙脚下,任何一点估算误差都会被那条曲线放大成事故。提前扩容买的不是产能,是把工作点从曲线陡的地方挪到平的地方。

✗ 这个直觉是错的
系统已经跑到 90% 了,说明快到极限了,加机器的边际收益一定很低——不如去优化代码。 恰恰相反:越靠墙,加机器的边际收益越高。在 90% 的地方加 10% 的机器就能让等待减半,这是优化代码几乎不可能达到的性价比。

这个错误直觉来自一个很自然的类比:把利用率想成一个「装满程度」。一个 90% 满的杯子,再倒 10% 就满了,所以「快到极限」。

但队列不是杯子。杯子满了就是满了,队列在 90% 的时候还有整整 10% 的产能在等着被用——问题不是产能用完了,是余量太少以至于吸收不了波动。而加机器直接买的就是余量。

同时要注意这句话的另一半:正因为边际收益高,边际损失也同样高。「靠墙时加机器特别划算」和「靠墙时特别脆弱」是同一条曲线的两个读法,你不能只享受前者。

◇ 结算

A大约 10%。精确地说,恰好是 10.0%——等于你现在的余量。加完之后利用率变成 81.8%,等待正好减半。

选 B 或 C 的人偏了 5 到 10 倍——而且偏的方向是高估,也就是说,你以为改善很贵,实际上很便宜。这个方向的偏差在实践中特别有害:它会让人放弃一个明明很划算的动作,转而去做工程量大十倍的代码优化。

B 「等待减半,产能大概也提这么多」——把两个量默认成同一个数量级了。它们的关系被 1/(1−ρ) 放大,而放大倍数取决于你站在曲线的哪个位置。有意思的是,这个答案在 ρ=50% 时恰好正确 C 「翻倍」——这是最常见的答案,来自「减半 ↔ 翻倍」的对称直觉。在 ρ=50% 的时候,翻倍产能会让 ρ 掉到 25%,等待变成原来的 1/3——已经超额完成了。 D 「加多少都救不回来」——过于悲观。90% 离墙还有距离;真正救不回来的是 ρ ≥ 1,那时候不管加多少机器,只要加完还是 ≥ 1,队列就仍然无限增长。

这一章的一句话

要把等待减半,需要增加的产能恰好等于你现在的余量——这既是「靠墙时加机器特别划算」,也是「靠墙时掉一点产能就是灾难」。

下一章离开纸面。前十章的公式全是我算的,你有理由不信。所以我在自己这台电脑上真的排了一遍队:真的 CPU busy loop 当服务、真的时刻表当到达、真的单线程服务台。10200 位顾客,然后拿 Lindley 递推逐个顾客对照它的开始时刻——中位误差是个位数微秒。而第一行会告诉你:同样忙到 80%、同样的吞吐,只要来得准走得准,800 个请求里没有一个等过哪怕一毫秒