故障现象
上线二十分钟后,部分访客访问英文文章 URL,却收到了中文 HTML。刷新有时会恢复正常,因此问题看起来像随机发生。它并不随机:响应缓存只使用文章 Slug,没有把请求的 locale 放进缓存身份。
应用本身对内容的查询是正确的。错误只在响应进入共享边缘缓存后出现,所以本地开发和第一次生产 smoke test 看起来都很健康。
时间线
- 09:00: 新版本进入生产环境。
- 09:08: 第一个中文文章请求写入边缘缓存。
- 09:20: 英文读者报告页面语言错误。
- 09:24: 团队使用全新浏览器环境复现。
- 09:31: 暂停新的缓存写入,同时保留已有证据。
- 09:42: 缓存键加入 locale 和规范化 pathname。
- 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 身份始终保持明确。