上线二十分钟之后

一篇虚构事故复盘,讨论缓存身份、双语路由、诚实的 404、证据保留,以及有边界的恢复过程。

发布于
2025年11月14日
更新于
2026年8月2日
阅读进度
2 分钟
标签
testing, network, web, engineering
Read the English version

故障现象

上线二十分钟后,部分访客访问英文文章 URL,却收到了中文 HTML。刷新有时会恢复正常,因此问题看起来像随机发生。它并不随机:响应缓存只使用文章 Slug,没有把请求的 locale 放进缓存身份。

应用本身对内容的查询是正确的。错误只在响应进入共享边缘缓存后出现,所以本地开发和第一次生产 smoke test 看起来都很健康。

时间线

  1. 09:00: 新版本进入生产环境。
  2. 09:08: 第一个中文文章请求写入边缘缓存。
  3. 09:20: 英文读者报告页面语言错误。
  4. 09:24: 团队使用全新浏览器环境复现。
  5. 09:31: 暂停新的缓存写入,同时保留已有证据。
  6. 09:42: 缓存键加入 locale 和规范化 pathname。
  7. 09:55: 跨语言验证通过,重新开启缓存。 最有价值的日志记录很短:
event=content.response
locale=en
pathname=/blog/sample
cache_key=article:sample
cache_status=HIT

请求语言与派生缓存身份彼此矛盾。这条证据已经足以把猜测变成可以验证的原因。

影响范围

范围 结果
可用性 页面成功返回
正确性 部分英文请求收到中文内容
安全性 没有私有数据泄露
持续时间 大约 35 分钟
根本原因 locale 没有进入缓存身份

为什么原有检查没有发现

  • 本地开发没有使用生产边缘缓存。
  • smoke test 只请求了一种语言。
  • 缓存键构造器测试了路径,却没有测试 locale 组合。
  • 验证过程把成功 HTTP 状态当成了充分条件。
  • 首次测试没有比较不同语言页面中的真实正文。

成功返回了错误含义的响应,仍然是一种失败。

修正行动

  • 缓存键包含 locale 和规范化 pathname。
  • 清理受影响响应。
  • 把一份错误响应去除敏感信息后保存为测试 fixture。
  • 增加跨语言缓存隔离自动化测试。
  • 在不包含敏感信息的诊断中记录派生键。
  • 记录如何停止缓存写入,同时保留缓存读取。
  • 生产 smoke check 必须验证正文语言。 恢复过程刻意一次只修改一个边界。团队先停止制造新的错误缓存,再确认原因,然后修改缓存键,最后恢复缓存。若在收集证据前就清理所有内容,这次事故反而会更难理解。

为什么 404 规则很重要

应用已经把缺失翻译视为真实 404,因此修复范围很小。团队只需纠正缓存,不必临时发明 fallback,也不必决定哪一种语言应该悄悄替代另一种语言。从路由到 repository,locale 身份始终保持明确。

Bojin Li

写软件、系统,以及那些还没定型的部分。