跳到主要内容

缓存击穿:这次冲击的是你的模型提供商,而不是数据库

· 阅读需 11 分钟
Tian Pan
Software Engineer

传呼机在 UTC 时间 14:02 响了。不是因为延迟,也不是因为错误——而是因为开销。费用仪表盘显示出一条垂直线:三分钟的输入 Token 计费大约是过去一小时平均水平的九倍,然后又恢复了正常。没有发布回归版本。没有新租户上线。流量精确到分钟来看都是平稳的。唯一改变的是,一个单一的 Prompt 前缀——集群中每个 Agent 共享的 14K Token 系统消息——在提供商端悄悄过期了,一千个 Worker 全都在同一个 200ms 的窗口内认定,自己就是那个需要将其写回的人。

这就是缓存雪崩(Cache Stampede)。这是自 2003 年 memcached 发布以来,运维人员一直在写事故复盘报告的那个老问题。2026 年的新变化在于,发生雪崩的缓存不再属于你。它存在于你的模型提供商内部,你无法检查其状态,而且每一次未命中(Miss)消耗的是真金白银,而不仅仅是几次额外的数据库查询。数据库工程师在二十年前就学会通过抖动(Jitter)来化解的同步 Bug,已经悄然出现在了一个没人想过要防御的账单细目中。

为什么 LLM 版本的雪崩比数据库版本更糟糕

在数据库时代,缓存雪崩是一个容量事件。一千个 Worker 没中同一个 Key,一千个相同的查询涌向 Postgres,连接池饱和,P99 延迟呈垂直上升,数据库要么在几秒钟内恢复,要么负载均衡器开始丢弃流量直到恢复。这在商誉上代价高昂,但在金钱上很少造成巨大损失。

LLM 版本则相反。容量不再是约束——提供商吸收了爆发流量。约束变成了价格曲线。缓存读取的成本大约是基础输入费率的十分之一。缓存写入的成本比基础输入费率高出四分之一,对于延长 TTL(Extended-TTL)的写入,成本有时甚至是两倍。命中与未命中的成本比率不是 1:1,而是接近 1:12。整个集群的一次协同未命中并不会触发告警。它只是在预热窗口期间悄悄翻倍你的输入 Token 支出,当你下次查看账单时,会发现一个谁也说不清来源的六位数“惊喜”。

另一个反转:你无法看到缓存。提供商不会告诉你哪个前缀在哪个 GPU 的 KV 缓存中,或者请求落在了哪个路由哈希上,或者 TTL 计时器何时开始。你只能从每个响应中看到两个数字——cache_creation_input_tokenscache_read_input_tokens——你必须通过它们来重构发生的事情。缓存是一个远程的、不透明的、随机的资源,你的代码会影响它的状态,但并不拥有它。

“羊群”是如何同步的

雪崩并非随机发生。它们的发生是因为某些因素同步了“羊群”,而 LLM 工作负载拥有比普通数据库工作负载更多的同步诱因。

集群滚动发布。 UTC 时间 14:00 发布了一次部署。每个 Worker 进程在 90 秒的窗口内重启。由于提供商端的路由哈希部分取决于租户或会话 ID,而提供商的负载均衡器刚刚将集群分散到新机器上,每个 Worker 的第一次请求都会错过 Prompt 缓存。一千个 Worker,一千次缓存写入,全部挤在两分钟内。

定时执行的批处理任务。 一个夜间评估任务在 UTC 时间 00:00:00 启动,并针对同一个模型并行发送一万个评估 Prompt。每个评估共享同一个 8K Token 的评分标准(Rubric)前缀。第一批 Worker 写入缓存;下一批在同一秒内到达,由于路由尚未收敛而未能命中。Google 的 SRE 手册自 2016 年以来一直在警告午夜定时任务(Midnight-cron)的同步问题——变化在于,现在被雪崩冲击的资源是按 Token 计费的。

TTL 本身。 Anthropic 的默认缓存 TTL 是 5 分钟。如果你的流量模式是突发性的——例如某个特定前缀每 7 分钟发送一次请求——你发送的每个请求都要支付写入溢价,且永远享受不到读取优惠。更糟糕的是,如果多个租户共享一个前缀,且他们恰好在同一分钟内都很忙,他们都会写入相同的缓存内容,因为提供商将他们的请求视为独立的缓存填充者(Cache Populators)。(租户的缓存是否真正共享取决于提供商的具体细节,你无法依赖这一点;请做最坏的打算。)

自动扩缩容。 当地时间 09:00 流量激增。编排器在 90 秒内从 20 个 Worker 扩展到 200 个。这 180 个新的 Worker 都在缓存预热之前使用相同的系统 Prompt 请求模型,而提供商的前缀路由层(Prefix-routing layer)无能为力,因为每个新 Worker 都是一个带有新 Prompt 缓存 Key 的新 TCP 级客户端。

共同因素:一致性。羊群是由相同的定时器、相同的 TTL、相同的部署窗口、相同的路由 Key 创造出来的。任何教科书式的缓存雪崩缓解方案都是从打破这种一致性开始的。

成本画像:用数字说话

假设你的服务运行一个带有 12K Token 系统 Prompt 和小型每轮用户消息的 Agent 循环。在稳定状态下,每个请求都是一次缓存读取:12K Token × 每百万 0.30×0.1=每个请求缓存部分花费0.30 × 0.1 = 每个请求缓存部分花费 0.00036。很便宜。

现在让两分钟内的所有读取都变成写入。每个请求变成 12K Token × 0.30×1.25=0.30 × 1.25 = 0.0045。单次请求成本上升了 12.5 倍。如果你的集群在预热窗口期间每秒处理一千个请求,那么在 120 秒内就会产生大约 270的额外支出——而如果缓存是热的,你只需支付270 的额外支出——而如果缓存是热的,你只需支付 21 即可获得这些输出。两个数量级的差距不是可以忽略不计的误差,而是你的缓存策略在部署收敛期间完全丧失了投资回报率(ROI)。

现在想象一下延长 TTL 的变体:写入是基础输入费率的 2 倍,读取仍为 0.1 倍。雪崩乘数跃升至 20 倍。你为了分摊写入成本而购买的 TTL 越长,每次意外写入的代价就越昂贵。

这是每个人在第一次启用 Prompt 缓存时都不会进行的计算。营销文案说“节省 90% 的输入 Token”。运维逻辑则说“在稳定状态下节省 90%,在同步未命中期间损失 1150%”。平均下来是否划算,完全取决于你发生雪崩的频率。

LLM 感知的缓存击穿防御究竟是什么样的

缓解措施并不新鲜。它们只是被那些以前从未考虑过缓存一致性的人重新发现罢了。

为 TTL 刷新引入抖动(Jitter)。 如果你每四分钟通过心跳包(keep-alive ping)来预热缓存(以维持在五分钟的 TTL 内),请为每个 worker 增加 ±30 秒的随机抖动。一千个 worker 在 14:04:00 准时发送 ping 会导致缓存击穿;而一千个 worker 在 14:03:30–14:04:30 之间随机发送 ping 则不会。这是一个只有一行代码的修复,却能消除大约 90% 的自发性缓存击穿。

概率性提前过期。 XFetch 算法——在 2015 年的一篇被所有人遗忘的论文中被证明是最佳的——让每个 worker 独立决定在名义过期时间之前刷新缓存,且概率随过期时间的临近而升高。其数学逻辑将刷新操作分散在一个区间内,而不是聚集在边界点。对于 LLM 提示词缓存(prompt caches),这表现为:在每次请求时,以概率 p(remaining_ttl) 发起一次刻意的缓存写入请求,即使缓存依然有效。过度刷新的预期成本很小,而同步失效(synchronized miss)的预期成本却很高。

在网关层实现 Single-flight。 在模型供应商前面运行一个 LLM 网关,合并并发的相同请求。如果一百个 worker 在同一个 50ms 窗口内携带相同的提示词前缀请求网关,只有第一个请求会触发供应商调用;其他九十九个 worker 都会等待这个正在处理中(in-flight)的结果。对于流式响应(streaming responses)来说,这更难实现(你必须将流分发给所有等待者),但对于部署发布场景来说,这才是正确的修复方案。

交错部署。 不要在一个 90 秒的窗口内重启整个集群,而是在 15 分钟内逐步重启,并设置一个持续两分钟的 1% 金丝雀节点。第一个启动的 worker 写入缓存,其余的则读取缓存。大多数平台团队出于容量原因已经采用了交错部署——只需将同样的逻辑扩展到提示词缓存预热即可,因为现在系统收敛(convergence)已经直接关系到资金成本。

显式预热。 在自动扩缩器(autoscaler)启动新 worker 之前,由特权调用者发送一个预热请求来填充缓存。然后再让新 worker 加入。这用一次刻意的缓存写入换取了数百次意外的写入。

关注写读比,而非绝对数值。 供应商的仪表板会显示每个响应的 cache_creation_input_tokenscache_read_input_tokens。这个比例就是你的缓存击穿测量仪。健康的稳态下,写入量应远低于读取量的 5%;而缓存击穿则表现为比例的突然反转。针对这个比例设置报警,而不是针对绝对支出——因为等到绝对支出触发报警时,缓存击穿通常已经结束了。

更深层的问题:没人负责衔接点

这种故障模式最令人不安的地方在于其责任归属。应用团队并没有写出导致击穿的代码,他们只是写了合理的逻辑,在需要时发起请求。基础设施团队也没有制造惊群效应(thundering herd),他们只是配置了一个按预期工作的自动扩缩器。成本团队也没有批准预算激增,他们只是签署了一个在稳态下能节省 90% 输入 token 费用的定价模型。

漏洞存在于这三个视角交汇的衔接点上,而这个衔接点就是供应商的缓存,没人对其负责。应用团队看不见它。基础设施团队无法调整它。成本团队无法对其建模,因为供应商的驱逐策略(eviction policy)没有文档说明,且会在不通知的情况下更改。(Anthropic 在 3 月初悄悄将默认 TTL 从一小时降至五分钟;许多客户是通过账单而不是变更日志发现这一点的。)

修复方案一部分是技术性的——抖动、合并请求、交错部署——另一部分是组织架构上的。必须有人端到端地负责提示词缓存契约:哪些前缀被缓存、它们的 TTL 是多少、预期的命中率是多少,以及当集群同步时支出会如何变化。在 2026 年的大多数团队中,这个角色并不存在。流量 SRE 负责容量,FinOps 工程师负责支出,应用工程师负责提示词,而供应商的缓存则悠然地夹在他们中间,被集体忽视,直到下一次报警事件发生。

如果你有一套提示词缓存策略,但今天却无法告诉我上次集群部署期间的写读比是多少——那么你并没有策略。你有的只是假设。而袭击你模型供应商的缓存击穿,正是让你清醒过来的账单。

References:Let's stay in touch and Follow me for more thoughts and updates