2025 年 5 月 29 日,Service Control 添加了一项新功能,用于进行额外的配额策略检查。该代码变更和二进制版本通过区域部署分发到了我们的区域,但由于需要触发代码的策略变更,因此在部署过程中从未执行过失败的代码路径。
为了安全起见,该代码变更附带一个红按钮(red button),用于关闭这条特定的策略服务路径。
该变更的问题在于,它没有适当的错误处理机制,也没有功能标志保护。
由于没有适当的错误处理机制,空指针导致二进制代码崩溃。
功能标志用于逐步按项目逐个区域地启用该功能,从内部项目开始,以便我们能够发现问题。如果该功能已受功能标志保护,该问题原本在试运行(staging)阶段就会被发现。
2025 年 6 月 12 日太平洋夏令时间上午 10 点 45 分左右,一个策略变更被插入到 Service Control 用于策略的区域 Spanner 表中。
鉴于配额管理的全局性,该元数据在几秒钟内就被复制到了全球。
该策略数据包含意外的空白字段。
Service Control 随后对每个区域数据存储区中的策略执行了区域配额检查。
这为这个相应的策略变更引入了空白字段,并执行了命中空指针的代码路径,导致二进制代码陷入崩溃循环。
鉴于每个区域部署,这变成了全球问题。
在 2 分钟内,我们的站点可靠性工程团队对事件进行了排查分类。
在 10 分钟内,我们确定了根本原因,并部署了红按钮(用于禁用服务路径)。
红按钮在事件发生后约 25 分钟内即可启用。
在事件发生后的 40 分钟内,红按钮启用已完成,我们开始看到各区域逐渐恢复,从较小的区域开始恢复。
在我们的一些较大的区域(比如us-central-1),随着 Service Control 任务重新启动,它对其依赖的底层基础设施(即Spanner表)产生了群体效应,导致基础设施过载。
Service Control 没有实施适当的随机指数退避机制来避免这种情况。
由于我们对任务创建作了限制,以最大程度地减小对底层基础设施的影响,并将流量路由到多区域数据库以降低负载,因此在 us-central-1 整整耗时约 2 小时 40 分钟完全解决问题。
此时,所有区域的 Service Control 和 API 服务已完全恢复。
相应的谷歌和谷歌云产品也开始恢复,但部分产品需要更长的时间,长短取决于其架构。
故障简要逻辑:
(1)一个新功能上线了(2025 年 5 月 29 日),用于检查 API 的配额策略,但这段代码在测试时没有真的被触发过,所以也就没发现里面有“空指针”问题(即程序里有个地方该填的数据没填,结果让程序崩溃了)。
(2)6 月 12 日 10:45 AM 左右,Google 向全球推送了一条新的“配额政策”数据。这个数据里意外包含了空值(空字段),结果引发之前那段“未曾被触发的故障代码”,让整个系统陷入“崩溃循环”(crash loop)。
(3)因为这套系统是全球部署的,所以这个崩溃瞬间波及全球,所有地区都出现了故障。