事故复盘将十二天前的一次部署确定为支出增加 3.6 倍的原因,而当时在场的参与者中,没有一个人在变更发布时参与其中。部署过程非常常规:蓝绿切换,流量按计划转移到绿色环境,蓝色环境停用,流水线变绿,发布工程师关闭了工单。生产环境的 SLO 都没有触发。应用层的告警也没有响起。系统运行得完全符合设计。
原本的设计是一个每五分钟运行一次的 Cron 任务,它每五分钟针对稳定的系统提示词前缀 (system-prompt prefix) 预热提供商的 Prompt 缓存。这种预热为团队在冷启动时带来了 91% 的缓存命中率,并在每个会话的第一次请求中获得了大约 4 倍的成本优势。该 Cron 任务是一年前首次引入蓝绿模式时编写的,其主机选择器 (host selector) 被固定在蓝色池 (blue pool),以避免在重叠窗口期间运行两次预热。当绿色环境变成活跃环境而蓝色环境消失时,该 Cron 任务失去了它的主机,并从“每五分钟运行一次”悄无声息地转变为“永不运行”。随着提供商缓存的 TTL 使预热的前缀过期,缓存命中率在接下来的 36 小时内逐渐下降。成本仪表盘计算的是每日窗口内的平均单次请求成本,平滑了趋势,直到下一个计费周期让问题变得显而易见。
这是一种处于两个互不感知的系统接缝处的失效模式:拥有滚动更新权限的部署流水线,以及拥有定期作业权限的调度器。每一方都完全按照预期行事。Cron 调度器遵守了其主机选择器。部署系统轮换了资源池。结果是出现了一个无人负责的隐蔽回归,因为回归存在于两者之间的关系中,而不是存在于任何一个系统内部。
缓存预热:部署系统无法察觉的部署依赖
现在 Prompt 缓存是首屈一指的成本杠杆。对于几千个 Token 的稳定系统提示词前缀,缓存命中的成本大约只有缓存写入成本的十分之一,且延迟仅为后者的一小部分。运行生产级 LLM 工作负载的团队通常报告称,当缓存工作正常时,支出可减少 60% 至 90%,且长前缀工作负载的延迟也相应得到改善。经济因素驱动着每一个严肃的应用程序在用户会话之间维持缓存热度。
Anthropic 的 Prompt 缓存默认 TTL 为五分钟,高吞吐量工作负载可选一小时的扩展 TTL。无论哪种方式,热度都是易逝的。如果在 TTL 窗口内没有请求触及缓存前缀,该前缀就会被逐出。下一个请求将支付缓存创建的溢价——大约比基础输入费率高出 25%——以及冷读取的延迟惩罚。一个希望在低有机流量期间保持稳定缓存预热的团队,最终会以短于 TTL 的间隔对前缀运行模拟探测 (Synthetic Ping)。
那个 Ping 就是预热作业。它不是请求路径的一部分。它不是应用程序的一部分。除了成本仪表盘之外,它在操作上对一切都是不可见的。而成本仪表盘在一段窗口内取平均值,是检测缓存预热回归最慢的机制。当每日平均值波动到足以跨越告警阈值时,缓存已经冷了一天。当每周平均值波动时,账单已经膨胀了十二天。
部署系统没有原生的缓存预热概念。它的正确性模型是“新代码是否成功启动并正在提供流量”。它对 Cron 任务的模型充其量是“清单中是否定义了 Cron”。这两者都无法检查 Cron 是否真的在针对正确的目标执行。固定在一个已不存在的部署颜色的 Cron 任务满足了这两个检查,并悄无声息地什么都不做。
失效模式:基础设施绑定到瞬态标识符
这里的深层模式比 Cron 任务和 Prompt 缓存更广泛。它适用于任何基础设施固定在周围系统视为瞬态的标识符上的情况:部署颜色、主机名、Pod 模板哈希、节点标签、被轮换的服务账号。被固定的基础设施在锚点变更后以“空操作”而非“故障”的形式存续,因为调度层的契约是“寻找匹配此选择器的目标并在那里运行”。一个无法匹配任何目标的选择器在契约中并不是错误;它只是一个空结果集。
同样的模式在多个地方重复出现。固定在一个工作负载不再携带的标签上的 Prometheus 抓取作业,会悄无声息地停止收集该工作负载的指标。固定在节点刷新期间被替换的主机名上的备份作业,会悄无声息地停止备份。固定在重命名后发生漂移的 Pod 选择器上的日志转发器,会悄无声息地丢弃日志。在每种情况下,可见的信号都是“一切正常”,而不可见的信号则是该作业所产生的任何产出都在缓慢衰减。
蓝绿固定的情况尤为隐蔽,因为“蓝”和“绿”是“活跃池”和“备用池”的概念别名,但名称却是物理存在的。固定到“蓝色”的 Cron 任务是固定在一个名称上,而不是固定在一个角色上。当发布版本切换角色时,该名称变成了坟墓,而该 Cron 任务现在正针对一个不再对应任何有意义事物的目标运行。发布工程师没有理由去思考它,因为 Cron 任务不是部署清单的一部分;而 Cron 任务的所有者也没有理由去思考它,因为部署过程从未询问过。
探测鸿沟:为什么缓存命中率作为告警信号具有滞后性
人的本能是对症状(缓存命中率)而非失效的机制发出告警。这种直觉是对的,但实现方式通常不对。大多数团队配置的是底部阈值告警:当缓存命中率在一段时间内低于某个绝对阈值时触发告警。在这种情况下,告警永远不会触发,因为命中率并不是瞬间下降的,而是逐渐衰退的。
衰退的形状至关重要。预热停止后,只要流量模式保持密集,自然流量就会继续命中前缀并刷新缓存。在非高峰时段命中率为 60%、高峰时段为 90% 的情况下,命中率可能会在几天内保持在阈值之上。只有当非高峰窗口延长到超过 TTL(通常发生在流量稀疏地区的深夜)时,它才会崩溃。这种模式是一个平滑的下降曲线,并伴随着缓存由于高峰期重建和夜间衰退而产生的每日波动。根据每日波动校准的底部阈值告警无法对这种趋势做出反应。
能够捕捉到这一点的方法不是监测阈值,而是监测斜率。该指标是将缓存命中率作为一级 SLI,并对多日窗口内持续的负斜率发出告警。24 小时内 5% 的下降是噪声,但连续三天每天 5% 的下降就是一种趋势。正是这种趋势能让回归在第三天而不是第十二天就暴露出来。
大多数团队不这样做,是因为在标准的观测堆栈中,缓存命中率并不是一个 SLI。它是一个存在于成本仪表盘中的衍生指标,通常提取自带有滞后一天的计费数据。要将其转变为可进行斜率告警的 SLI,团队必须在请求层进行监测,使用供应商在每个响应中返回的 cache_read_input_tokens 和 cache_creation_input_tokens 字段,并将其比例作为每个路由或每个模型的顶级指标输出。这项工作很直接,但很少有人去做,因为缓存预热在历史上一直是运维关注的问题,而不是应用关注的问题。缓存的经济效益已使其成为应用关注的问题,而观测手段尚未跟上。
填补鸿沟的模式 四种实践共同填补了这次回归所处的缝隙。它们都不是什么新鲜事,诀窍在于这四种实践都需要到位,因为每一种都填补了缝隙的不同部分。
将缓存命中率设为可进行斜率告警的 SLI。 将 cache_read_input_tokens / (cache_read_input_tokens + cache_creation_input_tokens) 作为单次请求指标输出,在路由级别进行聚合,并对持续的下降斜率发出告警。根据你流量的自然每日波动来校准斜率阈值——如果该指标通常在峰谷之间波动 10%,那么每天 10% 的下降就是“真实”的底线。对待持续的斜率告警要像对待延迟预算消耗一样:将其视为系统正走向违反 SLO 的信号,而非当前的违规。
在部署清单中将后台任务列为部署依赖。 当蓝/绿流水线切换颜色时,清单应列出针对该资源池的每个 cron、抓取任务、日志转发器和合成监控,如果其中任何一个没有重新固定到新颜色,部署应以故障模式关闭。这与当前的模式相反,在当前模式下,后台工作对部署是不可见的。使其可见意味着部署系统可以对其进行审计,而审计可以防止产生此次事故的孤儿状态。
将缺失目标视为故障,而非无操作(no-op)。 这是一个调度器级别的更改。如果一个 cron 被配置为针对一个解析为零目标的筛选器运行,调度器应当传呼负责团队,而不是静默地跳过执行。特别是 Kubernetes CronJobs,会非常乐意针对一个不再存在的节点池运行任务规范,并且除了状态字段外不记录任何内容;执行的缺失不会在任何可操作的地方体现出来。一个对比预定运行与实际完成情况并在持续差额时发出传呼的包装器是一段很小的代码,却能捕捉到很大一类故障。
针对供应商运行端到端合成监控。 预热任务的成功标准是“供应商在下一次调用中返回了缓存命中”,而不是“cron 退出状态码为 0”。一个每隔几分钟调用一次前缀、解析响应的 cache_read 字段并输出二进制缓存命中指标的合成监控,是唯一能证明预热确实在起作用的检查。cron 本身可以成功,而缓存却可能保持冷态,因为“发出了请求”和“填充了缓存”是两回事——只有后者才重要。
缓存预热是部署产物,而非运维副作用 这次事故中隐藏的架构领悟是,Prompt 缓存预热已成为一种部署产物。它是应用程序依赖的一块状态,其 TTL 短于许多流量模式中自然请求之间的间隔,且需要一个在部署系统中没有任何体现的任务来进行主动维护。如果一个团队将这种维护视为系统管理员的杂活——由 cron 在后台处理的事情——那么他们对待它的方式就像 2010 年“迁移即代码”成为默认标准之前,各团队对待数据库迁移的方式一样。这种不匹配会产生同样类型的事故:一次常规部署导致了部署系统并不知晓的依赖项发生了静默回归。
长远来看,解决方法是将缓存预热任务提升为一级部署产物。清单命名它,部署审计它,调度器将它的缺失视为故障。应用程序输出一个可进行斜率告警的 SLI,在账单暴涨之前检测到预热衰退。这些都不是什么了不起的基础设施工作,但回过头来看,正是这种细节工作决定了你是为预热的缓存付费,还是在每次颜色切换时都为冷缓存买单。
在第一天而不是第十二天捕捉到这一点的团队并没有做任何天才之举。它只是做了优秀运维团队一直在做的事情:将生产系统的隐式依赖显式化,以便部署系统能够对其进行推理。新奇之处在于哪些依赖项算数。在一个经济效益取决于他人数据中心内易过期缓存的系统中,缓存预热就是其中之一。
会员专享
余下内容仅对会员开放。 会员可读完整内容 —— 每一个公开发布的观点背后,那些框架、决策与推理的全貌。
— 完整文章,包含未公开存档的部分 — 可落地的工作框架,附带权衡与决策依据 — 新文章抢先看,先于公开发布 登录以继续阅读→ 随时取消 · 一次订阅,畅读全部