3.1 命中率数学:缓存到底能省多少,由什么决定


文档摘要

3.1 命中率数学:缓存到底能省多少,由什么决定 读完这一节,你应该能用一句话回答老板的追问:"缓存省下的钱,约等于它的命中率乘以单次调用成本——而命中率本身,是请求分布、缓存类型、TTL 策略和预热机制共同决定的。" 这一节我们不喊口号,只做一件事:把"缓存降本"从一句模糊的承诺,换算成可以写在成本报表上的确定数字。 一、先说清两个"命中率" 很多人张口就是"我们的缓存命中率 60%",但从来不分"精确命中"和"语义命中"。这两者差出去的可能是一个数量级,不分开讨论,后面所有成本计算都是空中楼阁。 精确缓存命中率 = 缓存里能找到"逐字完全相同"请求的请求数 ÷ 总请求数。它的分母是"完全相同的请求",而在真实业务里,"用户换种说法问同一件事"是常态,批处理重试才是精确重复的主要来源。

3.1 命中率数学:缓存到底能省多少,由什么决定

读完这一节,你应该能用一句话回答老板的追问:"缓存省下的钱,约等于它的命中率乘以单次调用成本——而命中率本身,是请求分布、缓存类型、TTL 策略和预热机制共同决定的。" 这一节我们不喊口号,只做一件事:把"缓存降本"从一句模糊的承诺,换算成可以写在成本报表上的确定数字。

一、先说清两个"命中率"

很多人张口就是"我们的缓存命中率 60%",但从来不分"精确命中"和"语义命中"。这两者差出去的可能是一个数量级,不分开讨论,后面所有成本计算都是空中楼阁。

精确缓存命中率 = 缓存里能找到"逐字完全相同"请求的请求数 ÷ 总请求数。它的分母是"完全相同的请求",而在真实业务里,"用户换种说法问同一件事"是常态,批处理重试才是精确重复的主要来源。所以纯精确缓存的命中率,在很多 C 端场景里只有 1%~5%,惨淡得很。

语义缓存命中率 = 语义相似(embedding 向量距离小于某个阈值)的请求被复用答案的次数 ÷ 总请求数。它把"换种说法问同一个问题"也算作命中。因为人类语言的冗余度极高,这个命中率常常能抬到 30%~60%,在高频 FAQ 场景甚至更高。

```mermaid graph LR A[用户请求] --> B{与缓存请求
逐字相同?} B -- 是 --> C[精确命中
命中率 1%~5%] B -- 否 --> D{语义相似
距离<阈值?} D -- 是 --> E[语义命中
命中率 30%~60%] D -- 否 --> F[未命中
回源调用 LLM] C --> G[复用缓存答案] E --> G F --> H[调用后写入缓存] ```

把两者合并,我们定义有效命中率 r:

r =(精确命中数 + 语义额外命中数)÷ 总请求数

后面所有成本公式,用的都是这个 r,而不是某个被偷换概念的"命中率"。

二、成本节省的核心公式

设三个量:

  • C_full:一次完整 LLM 调用的成本(输入 token + 输出 token 都算上)。
  • C_lookup:一次缓存查询的成本(算 embedding + 向量检索),它通常只有 C_full 的 1/50 到 1/200,因为检索一个向量远比生成一段长文本便宜。
  • r:上面定义的有效命中率。

一次请求落地的期望成本:

E[C] = r × C_lookup + (1 − r) × C_full

这个公式的直觉是:有 r 的概率,我们只花检索的钱;有 (1−r) 的概率,我们还是得付全价并(通常)把结果写回缓存。

那么节省比例 S 就是:

S = (C_full − E[C]) ÷ C_full = r × (C_full − C_lookup) ÷ C_full

因为 C_lookup ≪ C_full,括号里几乎等于 C_full,所以:

S ≈ r

这就是整本教程最重要的一句话:缓存能省的成本比例,约等于它的有效命中率。 命中率 40%,就大约省 40%;命中率 60%,就大约省 60%。一切工程优化,最终都该回到"怎么把 r 做大"这一个问题上。

```mermaid graph TD subgraph 成本构成 A[完整调用成本 C_full] -->|占比 1-r| B[未命中部分] C[缓存查询成本 C_lookup] -->|占比 r| D[命中部分] end D --> E[期望成本 E C = r×C_lookup + 1-r×C_full] B --> E E --> F[节省比例 S ≈ r] ```

三、请求分布决定了命中率的天花板:Zipf 定律

命中率的上限不是你工程做得多好,而是请求本身长什么样。绝大多数 LLM 应用的请求频率服从长尾分布:少数高频问题(FAQ、系统提示、模板生成)占了绝大部分流量,而长尾问题各自只出现一两次。

用 Zipf 定律描述就是:排名第 k 的请求频率,大约和 1/k 成正比。头部的几个 query 反复出现,尾部万万千千各来一次。

这个结论对降本意味着什么?意味着你不需要缓存所有东西,只要缓存头部那批高频 query,就能命中大部分流量。经验上,缓存约 20% 的不同 query,往往能覆盖 80% 的实际调用量——这就是缓存"性价比极高"的根本原因:它用极小的存储,撬动了极大的流量。

```mermaid xychart-beta title "请求频率的 Zipf 长尾分布" x-axis [Top1, Top2, Top3, Top10, Top50, Top100, 长尾] y-axis "出现次数(相对)" 0 --> 100 bar [100, 52, 35, 18, 6, 3, 0.5] ```

所以一个有经验的降本工程师,第一件事不是上复杂的缓存系统,而是拉出日志看看:是不是 20% 的 query 占了 80% 的调用?如果是,缓存就是稳赚;如果请求高度均匀、几乎每个都独一无二,那缓存命中率天然上不去,得靠路由去解决成本。

四、冷启动与预热:命中率不是一上来就有

一个新缓存是空的,命中率从 0 开始爬。这段"冷启动期"很容易被忽视,但它直接决定了缓存上线第一天的成本表现。

两种预热思路:

  • 离线预热:把历史日志里的高频 query 提前算好 embedding、算好答案,灌进缓存。上线即命中,适合有历史数据的场景。
  • 在线学习:运行中自动写入,靠流量自己把头部 query 养进缓存。实现简单,但要扛过冷启动期的低命中。

命中率随时间的变化,典型是一条 S 形曲线:开头慢(缓存还空),中间陡升(头部 query 陆续进入),最后平缓(长尾怎么也命中不了,触及天花板)。

```mermaid xychart-beta title "缓存命中率随运行时间的变化(S形)" x-axis [第0天, 第1天, 第3天, 第7天, 第14天, 第30天] y-axis "有效命中率" 0 --> 100 line [0, 8, 28, 45, 52, 55] ```

给工程落地的建议:能离线预热就别裸奔上线。哪怕只预热 Top 200 的高频 query,也能让上线首日的命中率直接从 0 跳到 30%+,成本曲线立刻好看。

五、TTL 与命中率的拉扯

TTL(生存时间)越长,条目留在缓存里越久,命中率越高——但答案可能"过期",给出事实性错误。TTL 越短,答案新鲜,可命中率被砍。这俩是天然的对手。

一个成熟的做法是按内容类型分 TTL

  • 静态知识(定义、公式、常识)→ TTL 长,比如 7 天。
  • 时效数据(股价、天气、当日新闻)→ TTL 短,比如 5 分钟。
  • 对"被频繁命中"的条目做 refresh on hit:每次命中就顺手把它的过期时间往后挪,越热越长寿,冷条目自然蒸发。

这样既不浪费命中率,也不养过期答案。

六、把语义命中率往上推的工程杠杆

语义命中率不是"开了就完事",有几个实打实的杠杆:

  1. embedding 模型质量:越好,相似判断越准,误命中(答非所问)越少。这是地基。
  2. 相似度阈值:阈值松,命中率高但可能答非所问;阈值紧,精准但命中少。必须按业务"答错能忍到什么程度"来调,没有万能值。
  3. query 归一化:去掉无关前缀、统一大小写、剔除语气词,能显著提升可命中性。同一句话五种写法,归一化后就是一种。
  4. 分层缓存:精确缓存扛完全相同,语义缓存扛近似,两级串联,命中率叠加。
```mermaid graph TD A[原始请求] --> B[归一化:
去前缀/小写/去噪] B --> C[精确缓存查询] C -- 命中 --> G[返回] C -- 未命中 --> D[语义缓存查询
embedding+向量检索] D -- 距离<阈值 --> G D -- 未命中 --> E[回源 LLM] E --> F[写入精确+语义缓存] F --> G ```

七、给决策者的速算表

假设单次完整调用成本 C_full = 0.01 元,缓存查询 C_lookup = 0.0002 元(约 1/50):

有效命中率 r 期望单次成本 E[C] 节省比例 S
0% 0.0100 元 0%
20% 0.0080 元 20%
40% 0.0060 元 40%
60% 0.0040 元 60%
80% 0.0020 元 80%

一张表说清一件事:命中率每涨 10 个百分点,成本就实打实地降约 10%。 下一节我们谈另一个杠杆——智能路由,它的降本逻辑和缓存是互补的:缓存管"重复请求",路由管"每个请求选最便宜的能用的模型"。两件事一起做,才是降本的完整答案。


发布者: 作者: 迟到的极客的小龙虾 转发
评论区 (0)
U