“少写一行代码,5分钟狂刷一次下载,开发者8000美元就这么烧没了!”
由于一个 Bug,macOS 录屏应用 Screen Studio 的自动更新机制出了问题: 每隔 5 分钟就会让每位用户重复下载一次更新文件。这个更新包有 250MB,大量重复下载导致请求次数飙升至 900 万次,最终生成了超过 2PB(约 200 万 GB)的 Google Cloud 流量。
当时的流量波动如下图所示,虽然这个截图本身看起来没什么大不了,但背后的流量规模可不容小觑:在一个多月的时间里,Screen Studio 的服务端持续跑出至少 100 Mib/s 的带宽,有时甚至逼近 1 GiB/s——而且是每一秒都在大规模传输数据。
这个 Bug 简单得令人痛心,甚至可以说愚蠢至极。
简单来看,Screen Studio 是一款运行在 macOS 上的本地屏幕录制应用。出于常规需求,身为开发者的 Adam Pietrasiak 为它设计了一个自动更新机制,让用户可以方便地获取最新版本。
更新逻辑其实也很简单:应用会每五分钟检查一次更新,或者在用户启动时主动检查。一旦检测到有新版本,就会下载更新文件,并暂停五分钟一轮的检查循环,直到用户完成安装并重启应用为止。
悲剧性的重构
最初,Screen Studio 的自动更新机制还有个小问题:一旦检测到新版本,应用会立刻弹窗提醒用户更新。但这在用户录屏时出现弹窗将会非常扰人——毕竟谁也不希望录到一半被窗口打断。
为了改善这一体验,Adam Pietrasiak 决定对更新逻辑进行了重构。不过,就是在这次重构中,出了个代价惨重的疏忽:他漏掉了一行关键代码——在更新文件已经下载后,应该停止后续的定时检查。
忘记这行代码的后果就是:应用每五分钟就会重新下载一次同一个 250MB 的更新包。
悲剧性的运行环境:后台运行数周
问题在于,事实证明有成千上万的用户把 Screen Studio 这款应用长期挂在后台运行,即使他们并没有主动使用。
这间接地导致 Bug 的影响被大大放大——这些客户端每隔五分钟就自动下载一次 250MB 的更新文件,而且是持续了好几个星期。
来做个简单的计算:
每五分钟一次的频率,一天大约会执行 288 次下载。
每个更新文件约为 250MB,也就是每天每位用户产生约 72GB 的下载量。
这一 bug 持续了一个多月,而开发者 Adam Pietrasiak 完全没有察觉到。
该应用大约有一千个这样的客户端实例始终在后台运行。
250MB × 288 次/天 × 30 天 × 1000 用户
换算之后就是——2,000,000GB
或 2,000 TB
或 2PB 的流量
一连串糟糕的错误
雪上加霜的是,Adam Pietrasiak 当时并没有在 Google Cloud 上设置任何流量或账单预警机制——毕竟,过去每月的账单从未超过 300 美元,也就没太放在心上。
而流量监控呢?也没人主动去看,因为“以前从来没出过问题”。
那这场事故最后是怎么被发现的呢?并不是通过技术手段,而是因为 Adam Pietrasiak 的信用卡因为超过了其设定的限额而被银行拒付了(所幸他事先设了限额,否则后果可能更惨)。
对用户的影响
这不仅对我们来说是场灾难,对一些用户来说更是严重的问题。
对于一部分用户来说,它甚至成了现实生活中的麻烦:由于更新文件是从用户设备端发起的下载,超大流量实际上是“从他们自己的网络里跑出去”的。
有位用户住在郊区,结果因为异常高频的网络使用,被当地的运营商直接断了网,甚至取消了服务合同——更糟糕的是,那一片区域没有别的可选网络提供商。
得知此事后,Adam 表示将对事件负责,并承担由此产生的全部费用。幸运的是,那位用户最终还是与运营商协商解决了问题,没有造成进一步损失。
尽管如此,对开发者和用户来说,这都是一次糟糕的经历。Adam 后来说,作为一名设计师,他一直非常在意用户体验——但这次的事故,不只是糟糕体验,甚至一度对用户造成了实际伤害。
总结教训
基于此,Adam Pietrasiak 自己也总结了一些经验,也希望能给其他开发者提个醒:
始终为云服务设置费用预警;
写自动更新逻辑时要极其小心;
任何可能带来成本的代码都要格外谨慎;
设计可由服务器远程触发的特殊信号机制,比如“强制更新且不提示用户”;
定期检查你的云服务状态。
最后
随着 Adam Pietrasiak 把这次事故分享出来,也引发了不少开发者的讨论。
有网友指出,这次事件的损失可能远不止账单上写的 8000 美元。比如用户用的是流量,像移动热点,很可能因此悄悄多花了不少钱,却完全不知道是应用的问题。
也有人认为,像自动更新器这种容易出问题、又特别关键的功能,其实没必要硬着头皮自己做。用个成熟、靠谱的第三方方案,反而更省事、安全。毕竟,一旦 App 出了严重 Bug,更新机制就成了唯一的补救通道,设计得不靠谱,后果可能比想象中严重得多。网友 jmull 指出,「像“每五分钟检查一次更新”这种设计,就挺离谱的,说明他们可能根本没认真设计这个系统。」
还有开发者将其归咎为没有代码审查。用户 donatj 表示,「我一直对代码评审非常严格。有一次,一个经理随口说我应该多留点问题给 QA 处理,语气差不多是“最坏能出什么问题?”我当时脱口而出:“我们全都丢掉工作。”我们永远离“因为一行糟糕的代码丢掉工作”只差一步。我抓到初级甚至资深开发者写出可能泄露用户隐私信息(PII)的代码的次数,简直数不清。在大多数系统中,一不小心就可能惹上法律麻烦,这实在太容易了。」
这次事件虽然是个意外,但对很多人来说,也是一个值得认真思考的教训。无论是系统设计、代码审核,还是日常监控和应急预案,做得细一点、稳一点,总归是有好处的。