IT 论文精读 · PAPER 61
Rzadca 等 · 华沙大学 & Google · EuroSys 2020
2020 年,Google 公开了 Autopilot——一个替工程师自动决定「每个程序该分多少内存、多少 CPU」的系统。论文写作时它已管着 Google 全球机群里超过 48% 的资源用量,把「被预定了却一直没用上」的那部分从 46% 压到 23%——按机器数折算是上万台的量级。
云上最大的浪费,往往不是机器闲着,而是被人预定了、却一直没被用掉。
你上线一个服务,平台让你填一张表:这东西要多少内存、多少 CPU?说实话,没人真的知道。而填错的两种后果完全不对等:填少了,内存一超程序当场被杀、CPU 一超被卡到爬,半夜被叫醒的是你;填多了,只是账单上多一笔,当场没人骂你。
于是每个人都往大了填——单独看谁都理性又无辜,加起来却是天文数字的空转。云上的浪费主要不是「机器伸缩得不够快」,而是「人不会填这张表」。
Autopilot 的点子朴素得几乎无聊:别再让人猜了。系统天天看着每个程序真实用了多少,自己把这个数填上、并随时改。就像水电公司不会让你申报下个月要用多少度电,而是照着你过去的用量给你定档。
三步,每一步都很像人的常识。
第一步记账:给每个程序记一本「过去用了多少」的账本,而且越旧的记录颜色越淡——上个月某天的一次偶发高峰,不该永远绑架今天的配额。
第二步两种资源、两种胆量:内存看历史最高值,因为内存一超就是当场被杀,这个赌打不起;CPU 看「绝大多数时候都不超过的那条线」,因为 CPU 超了只是慢一点、能忍,不必按最高峰买单。定完档再往上留一点安全余量。
第三步拿不准就开个会:定档的规则不止一套(账本褪色的快慢、余量留多留少都可以不同)。Autopilot 干脆让好几套规则一起上,各自把过去几周重新推演一遍——「要是当初听你的,会闯几次祸?又白留多少?」谁的账最好看就多听谁的。而且改主意本身也要记一笔代价,免得它今天听 A、明天听 B 地来回横跳。
「照着历史用量定配额」谁都想得到。这篇论文值钱的地方,是它在十万台机器量级的真实环境里跑通了,而且让工程师肯把手松开。
秘诀是把那份「不对等」明明白白写进规则:省过头的代价,被算得比浪费重得多——这架天平从设计上就是歪的。再配上「先离线推演 → 再空跑不生效 → 再小范围对照 → 最后灰度铺开、随时能一键退出」的上线节奏。结果它不但没多杀程序,反而让被内存超限严重折腾的程序少了约十倍——很多人当初拍脑袋填的数,还不如机器估的准。
也说句诚实的:它只会照着历史往下推。第一次上线、或形态突变的程序没有历史可抄,只能先按保守的默认值来。
云上的浪费大头不是「伸缩不及时」,而是「人填不准资源申请」——填少了挨打、填多了没人管。Autopilot 把这张表交给机器:看会褪色的历史用量,内存按峰值、CPU 按高分位,再留一点安全边际,还让好几套规则回放历史比谁代价更小。Google 靠它管住了近半个机群,浪费从 46% 降到 23%,而程序被内存杀掉的情况反而更少。
想看衰减直方图的算法、多模型推荐器的代价函数和完整实验数字? → 切到精读版
Autopilot 是 Google 在 Borg 之上运行的自动伸缩系统,它替工程师自动设定每个任务的 CPU / 内存配额(垂直伸缩)与任务副本数(水平伸缩):做法是把每个任务的历史用量攒成一个按时间指数衰减的直方图,从中取统计量(内存取峰值、在线服务的 CPU 取高分位)再乘上安全边际;同时用一组参数各异的模型回放历史、按「代价」加权投票来挑推荐值。论文发表时它已覆盖 Google 超过 48% 的机群资源用量,把 slack(配额与实际用量之差)的中位水平从人工管理的 46% 降到 23%,同时把被 OOM 严重影响的作业数减少了约一个数量级。
(limit − usage) / limit。它被调度器占着、却没人在用,是这篇论文要压缩的目标。作者是 Krzysztof Rzadca(华沙大学 / Google)领衔的一支 Google 团队,合作者包括 Borg 论文的作者 John Wilkes,发表于 EuroSys 2020。它上承 Borg(2015)——Borg 解决的是「这些容器该塞进哪几台机器」,而 Autopilot 补上了 Borg 一直没解决的另一半:这些容器一开始要的数字本身就是错的。它下启的是今天所有云上的「资源右调(rightsizing)」实践:Kubernetes 的 VPA(Vertical Pod Autoscaler)推荐器用的正是这套「衰减直方图 + 取分位数 + 加安全边际」的路子。
Borg 这类系统要求提交作业时声明配额(limit)。声明少了,任务会被 OOM 杀掉或被 CPU 节流;声明多了,调度器会替你把那份资源占着不给别人。于是激励是彻底不对称的:填小的代价是页面报错、值班被叫醒、事故复盘;填大的代价只是一张没人细看的账单。
结果是所有人都往大了填,而且填完不再回来改——负载变了、代码优化了、流量迁走了,那个数字仍停在两年前某次事故之后拍的脑门上。论文用一个词概括这段空当:slack。人工管理的作业 slack 高达 46%:近一半被预定的资源从来没被用过。
为什么不能靠 Borg 已有的「资源回收」解决?Borg 确实会把申请了却没用的部分回收给低优先级的批处理任务,但那只是把浪费掩盖起来:高优先级作业的 limit 仍是硬预留,仍占着调度容量、仍决定着容量规划要买多少机器。回收治症状,改小 limit 才治因。
Autopilot 最关键的一步不是算法,而是承认这件事的代价是不对称的,并把这份不对称写成明文。它给每个候选配额算一个代价,大致是三项之和:
有了这个代价函数,「该填多少」就从一个靠胆量的判断题,变成了一个可以拿历史数据回放、算分、比较的优化题。这是整篇论文的枢纽。
第一套推荐器朴素而好用。它给每个任务持续采样 CPU 与内存用量,把样本攒进一个直方图(不存原始时间序列,只存分布,省空间又能随时取分位数)。
关键的一笔是指数衰减:每个样本的权重按 w(τ) = 2^(−τ/t½) 随时间变轻——τ 是这个样本有多老,t½ 是半衰期。白话说:直方图会「褪色」,一个月前的偶发尖峰不会永远绑架今天的配额,但也不会立刻被忘掉。论文给的典型值是 CPU 半衰期约 12 小时、内存约 48 小时——内存记得更久,因为忘得太快会付出被杀的代价。
取哪个统计量,取决于「超限了会怎样」:
算出的数字再乘一个安全边际(量级在一成上下;用量越小的任务边际给得越宽,因为小任务的相对波动更大),然后向上取整到一个规整档位。整套东西没有一行难懂的数学——它的分量全在「哪个资源该用哪种胆量」这个判断上。
最后是怎么落地。因为改配额可能触发任务重启,Autopilot 是分批滚动地改(不会一口气动完一个作业的所有副本),而且对调小内存格外谨慎——调大随时可以,调小要更充分的历史证据。
滑动窗口推荐器有一堆旋钮:半衰期多长?取 p90 还是 p95?边际留多少?不同作业的最优旋钮位置并不一样——流量平稳的服务和每天两个尖峰的服务,需要的褪色速度完全不同。人工给每个作业调旋钮,等于把「填表」的问题换了个地方而已。
第二套推荐器(论文说其思路受强化学习启发)的解法是:不选一套参数,而是养一群模型。每个模型带一组固定参数——自己的衰减速度、自己的安全边际、自己把直方图折算成 limit 的方式。对每个作业,系统把它过去几周的真实用量拿出来回放,问每个模型:「如果当初听你的,会超限几次、白留多少、改了几次 limit?」按前面那个代价函数算出分数,代价小的模型话语权大,最终推荐值由这群模型按分数加权得出。
还有一笔关键罚分:换模型本身也要计代价。否则系统会因分数上的微小抖动今天听 A、明天听 B,把配额来回拉扯——而每次拉扯都可能重启任务。
垂直伸缩管「一个副本多大」,水平伸缩管「有几个副本」。后者按一个信号定副本数——默认是 CPU 利用率(总负载除以目标利用率),也允许作业主自定义指标(队列长度、每秒请求数等)。
这里最值得学的是它刻意做成不对称的:上调看短窗口的近期负载、几乎立刻跟上;下调看的却是好几天窗口内的峰值,而且一小步一小步地降。理由很实际——加副本加错了只是多花点钱,减副本减错了就是流量回来时抓瞎。同一条「代价不对称」原则,在水平方向上又用了一遍。
这一节常被跳过,但它才是这篇论文与一篇算法论文的真正分野。把配额控制权从工程师手里拿走,技术上不难,难的是让人同意。Autopilot 的做法是一条递进的证据链:先在历史轨迹上离线仿真;再在生产里空跑(dry-run)——只记录「我本来会怎么设」、不真的生效,让作业主自己对着看;再做对照实验;然后分批灰度接管,配上看得见的仪表盘,并且随时能一键退出。小的、新的作业则默认纳管、允许选择退出。这是一份关于「自动化如何取得信任」的工程学,不只是关于配额怎么算。
第一,它补上了 Borg 的另一半。Borg 把装箱做到了极致,但它接收的输入——每个作业声明的 limit——一直是错的。再好的调度器也救不了一份系统性夸大的需求单。Autopilot 修的是输入本身。
第二,它把「资源右调」从一门运维手艺变成了一个可优化的问题。一旦能写出代价函数、能在历史数据上回放打分,这件事就可以自动化、可以持续做,而不是靠某个人某个季度的良心。今天 Kubernetes 的 VPA、各家云的资源推荐与 FinOps 工具走的都是这条路——VPA 的推荐器几乎就是这套「衰减直方图 + 分位数 + 安全边际」的公开实现。
第三,最容易被低估:它给出了一个「自动化怎么被接受」的样板。离线仿真 → 空跑 → 对照 → 灰度 → 可退出,这条路径本身就是可复制的方法论。
① 一句话:Autopilot 自动设定 Borg 上每个任务的 CPU / 内存配额与副本数,靠的是「会指数衰减的历史用量直方图 + 取统计量 + 安全边际」。
② 痛点:云上的大头浪费不是伸缩慢,而是人填不准 resource request——填少了被 OOM 杀 / 被节流,填多了只是账单难看,激励彻底不对称,于是全都往大了填。
③ 枢纽:把这份不对称写成明文代价函数(超限的痛 ≫ 浪费,再加改动罚分),「填多少」于是从判断题变成可回放、可打分的优化题。
④ 垂直伸缩:用量攒进会褪色的直方图(w(τ)=2^(−τ/t½),CPU 约 12h、内存约 48h);内存取峰值、在线服务 CPU 取高分位、批处理取均值;乘安全边际后滚动下发,调小内存尤其谨慎。
⑤ 多模型推荐器:养一群参数各异的模型,各自回放历史算代价,按代价加权投票;换模型也要罚分,防止来回横跳。
⑥ 水平伸缩:同一条不对称原则——上调看短窗口、立刻跟上;下调看多日峰值、小步慢降。
⑦ 结果:覆盖 Google 超过 48% 的资源用量;slack 46% → 23%(一组对照中绝对量从约 12000 台机器降到约 500 台);被 OOM 严重影响的作业减少约一个数量级,>99.5% 的作业-天无 OOM。
⑧ 影响与局限:Kubernetes VPA 与各家云的资源推荐几乎照抄这套;但它只会照抄历史(冷启动无解)、看到的用量已被节流扭曲、改配额可能重启任务,而且权重与超参仍是人定的——自动化搬走了猜测,没有消灭它。