Skip to content

检索缓存策略专题

版本:v1.2

最后更新:2026-07-09

很多 RAG 系统一开始只关注“能不能检索到”,等流量起来后才会发现另一个现实:

  • 同类问题被反复检索
  • 成本和延迟一起上升
  • 一加缓存,旧结果和旧权限又可能一起被缓存下来

这篇专题聚焦检索缓存策略,重点不是“怎么把结果存起来”,而是:

  • 什么该缓存
  • 什么不该缓存
  • cache key 怎么做
  • 知识更新和权限收缩时如何失效
  • 怎样避免把缓存做成新的风险源

1. 什么是检索缓存

更实用的理解通常是:

  • 对检索结果、中间候选、改写结果或重排结果做复用

重点不是“少做一次调用”本身,而是:

  • 在不明显伤害正确性的前提下降低成本和延迟

检索缓存通常位于这类链路中:

text
User Query
 -> Query Rewrite
 -> Retrieval
 -> Rerank
 -> Context Assembly
 -> Generation

缓存点既可以在:

  • rewrite 之后
  • retrieval 之后
  • rerank 之后

也可以在更靠近最终上下文装配的位置。

2. 为什么检索缓存比普通接口缓存更难

普通接口缓存通常主要看:

  • 请求参数
  • TTL

但检索缓存通常同时受下面这些因素影响:

  • 查询表达
  • 会话上下文
  • 权限范围
  • 知识版本
  • 检索策略版本
  • rerank 版本
  • metadata 过滤条件

只要其中任一维度发生变化,老缓存就可能:

  • 不再正确
  • 不再安全
  • 不再值得复用

所以检索缓存不是“给向量检索前面加个 Redis”那么简单。

3. 检索缓存和 Prompt Caching 不是一回事

这是非常容易混淆的一点。

根据 OpenAI Prompt caching 官方文档在 2026-07-07 的说明,Prompt Caching 依赖的是:

  • prompt 前缀完全一致
  • 静态内容放前面、动态内容放后面
  • 缓存只对足够长且前缀精确匹配的请求生效

它优化的是:

  • 模型推理前缀复用

而检索缓存优化的是:

  • 检索链路本身的重复查询和重复中间结果

所以两者的关系更像:

  • Prompt Caching:模型调用层缓存
  • Retrieval Cache:知识检索层缓存

这两个能力可以同时存在,但互相不能替代。

4. 什么内容适合优先缓存

不是所有东西都值得缓存。更适合优先考虑缓存的通常包括:

  • 高频稳定查询的检索结果
  • 查询改写结果
  • rerank 前候选集
  • metadata 过滤后的候选结果
  • 热门 FAQ 的最终证据包

这几类缓存之所以更有价值,通常是因为:

  • 重复率高
  • 变动频率低
  • 延迟收益明显
  • 风险相对可控

而下面这些内容通常要更谨慎:

  • 带强会话上下文的问题
  • 高敏感、高权限数据的结果
  • 依赖实时状态的业务查询
  • 容易受知识版本变化影响的场景

5. 一个更实用的缓存分层

把缓存拆层,比只做一个“检索结果缓存”更稳。

5.1 Query Rewrite 缓存

缓存对象:

  • 原始 query 对应的改写结果

适合:

  • 热门 FAQ
  • 高重复口语问法
  • 术语映射稳定的场景

价值:

  • 减少重复 rewrite 成本
  • 保持高频 query 改写稳定

5.2 Retrieval 候选缓存

缓存对象:

  • 原 query 或改写 query 的候选文档列表

适合:

  • 数据相对稳定
  • 初筛检索成本明显
  • 热点问题重复高

价值:

  • 降低向量检索或混合检索重复开销

5.3 Rerank 结果缓存

缓存对象:

  • 候选集重排后的前 N 项结果

适合:

  • rerank 成本较高
  • 热门 query 重复率高

价值:

  • 降低 rerank 成本
  • 提高热门问题稳定性

5.4 Evidence Pack 缓存

缓存对象:

  • 最终准备用于回答的证据包

适合:

  • 标准 FAQ
  • 政策类固定问法
  • 需要稳定引用的热点问题

价值:

  • 直接减少整条 RAG 上游链路的重复工作

6. cache key 设计是检索缓存的核心

缓存策略里最关键的通常不是“存哪里”,而是:

  • key 怎么设计

一个不合格的 key,命中率可能高,但系统会不安全;一个过度保守的 key,系统会安全,但几乎打不中。

更实用的 key 维度通常包括:

  • 规范化 query
  • 租户或组织范围
  • 权限范围
  • 知识版本
  • 检索策略版本
  • rerank 版本
  • metadata 过滤条件

例如:

text
tenant=dispatch
| query=企业版订单开票后是否支持退款
| scope=finance_viewer
| kb_version=2026-07-07
| retrieval=v3
| rerank=v2

如果少了这些字段,常见事故会变成:

  • 旧版本政策结果被继续复用
  • A 租户命中的结果被复用给 B 租户
  • 新策略上线后还在吃旧缓存

7. 权限和版本必须进入缓存 key

很多检索缓存事故不是命中率问题,而是:

  • 旧权限结果被复用给了不该看的用户
  • 旧知识结果在新版本下继续返回

所以更稳妥的做法通常是:

  • 缓存 key 里显式带上权限边界
  • 缓存 key 里显式带上知识版本或索引版本

至少要能回答:

  1. 这份缓存对应的是哪一版知识库?
  2. 这份缓存是否只适用于当前租户?
  3. 当前用户是否有权看到这组结果?

如果这三个问题答不清楚,就不应该轻易缓存。

8. 缓存策略应当和失效机制成对设计

缓存最容易做坏的地方,不是“命中不高”,而是:

  • 该失效的时候没有失效

检索缓存至少要考虑这些失效触发:

  • 知识库内容更新
  • 文档删除
  • 权限收缩
  • 检索策略升级
  • rerank 模型变更
  • metadata 规则变更

失效方式通常有三种:

8.1 TTL 失效

最简单,但风险是:

  • 变更发生了,缓存还没过期

8.2 版本失效

通过知识版本或索引版本切换自动失效。

优点:

  • 更可控

缺点:

  • 需要版本治理做得足够清楚

8.3 主动失效

在知识变更或权限收缩时主动清理相关缓存。

优点:

  • 最精确

缺点:

  • 工程复杂度更高

真实系统里往往是:

  • 版本失效 + TTL + 热点主动刷新

三者组合使用。

8.4 权限收缩和知识删除最适合走事件驱动失效

很多缓存问题真正危险的不是:

  • 热点 FAQ 多缓存了几分钟

而是:

  • 权限已经收缩,但旧结果还在命中
  • 文档已经删除,但旧证据包仍被复用

这类场景里,更稳妥的做法通常不是只等 TTL,而是把这些动作做成事件:

text
ACL / Knowledge Change Event
 -> locate affected docs / chunks / tenants
 -> invalidate retrieval cache
 -> invalidate evidence pack cache
 -> optionally trigger hot-path rebuild

尤其涉及下面这些变更时,更适合直接触发主动清理:

  • tenant 权限收缩
  • 文档下线
  • 法务/政策内容撤回
  • 高风险 FAQ 更正
  • retrieval filter 逻辑切换

9. 常见缓存模式:cache-aside 和预热缓存

Redis 官方文档里,比较常见的思路包括:

  • cache-aside
  • prefetch / pre-caching

9.1 Cache-aside

根据 Redis 官方文档,cache-aside 的典型流程是:

  1. 先查缓存
  2. miss 时查主存储或主检索链路
  3. 结果写入缓存
  4. 下次命中直接返回

适合:

  • 你不确定哪些 query 最热
  • 想按需填充缓存
  • 想控制缓存空间成本

9.2 预热缓存

适合:

  • 热门问题高度可预测
  • FAQ 或政策类内容稳定
  • 想在高峰前把热点结果准备好

这两种方式并不冲突。很多系统最终会变成:

  • 热点问题预热
  • 长尾问题 cache-aside

9.3 stale-while-revalidate 往往比“要么命中要么重查”更实用

在高流量系统里,很多团队最后会走到一种更折中的策略:

  • 先短时间返回可接受的旧结果
  • 后台异步刷新最新检索结果

这更接近 stale-while-revalidate 的思路。

它更适合:

  • FAQ、制度、帮助文档这类变动相对可控的知识
  • 热点 query 很多、重查成本高的链路

但要注意两条边界:

  • 权限收缩和删除场景通常不适合继续返回 stale 结果
  • 高风险实时状态查询不应借 stale 缓存冒险

9.4 negative cache 也值得单独设计

很多检索系统不只会重复命中“有结果”的问题,也会反复遇到:

  • 本来就没有答案的问题
  • 某类查询在当前知识域下一直空检索

这时可以考虑:

  • 对空检索或确定性失败结果做短 TTL negative cache

它的价值通常在于:

  • 避免长尾 bad queries 反复打底层检索

但也要特别谨慎:

  • 如果知识正在快速增长,negative cache 太长会掩盖新内容上线
  • 如果空检索是权限导致,而不是内容缺失,negative cache 要带权限上下文

10. TTL 不应统一拍脑袋设一个值

不同知识域的 TTL 应明显不同。

可以按知识变动频率粗分:

场景更适合的 TTL 思路
高频变动工单/运行状态短 TTL 或不缓存
企业政策 FAQ中 TTL + 版本控制
合同和制度文档中短 TTL + 强版本失效
静态帮助文档长 TTL

真正该问的不是:

  • TTL 设几分钟

而是:

  • 这类知识多久可能变脏
  • 脏一次的代价有多大

10.1 TTL 最好和风险等级一起决定

同样都是“知识问答”,TTL 也未必一样。

例如:

场景更适合的策略
公共帮助文档长 TTL + 定时预热
内部制度文档中 TTL + 版本失效
工单 / 工程运行状态短 TTL 或直接不缓存
涉及权限与审批的知识短 TTL + 权限事件失效
高风险政策与合规说明短 TTL + 主动刷新 + 严格版本绑定

10.2 soft TTL 和 hard TTL 可以分开

更成熟的缓存体系通常不会只用一个过期时间。

更常见的做法是:

  • soft TTL:过了之后允许后台刷新
  • hard TTL:过了之后绝不继续返回

这样可以同时兼顾:

  • 热点 query 的可用性
  • 高风险知识的 freshness 边界

11. 检索缓存如何和查询改写联动

很多热门问题的原始 query 表达非常分散,但经过 rewrite 后会收敛到少数标准表达。

这时很适合把缓存挂在改写后 query 上,而不是原始 query 上。

例如:

  • 这个开票后还能退吗
  • 企业版发票开了还能退款么
  • 开票之后还能不能退

经过 rewrite 之后都可能归一到:

  • 企业版订单开票后是否支持退款

这会显著提高缓存命中率。

但要注意:

  • 改写错误会把缓存错误放大

所以 rewrite 缓存和 retrieval 缓存都应该保留回放链路。

11.1 不要把“语义相近”直接等同于“能共用缓存”

这是检索缓存里一个很常见的误区。

两条 query 看起来很像,不代表它们真的可以共用:

  • 权限边界可能不同
  • 时间范围可能不同
  • filter 条件可能不同
  • 一个问的是政策,一个问的是实时状态

所以更稳妥的策略通常是:

  • 先做 rewrite 归一
  • 再做显式条件对齐
  • 最后再决定是否允许复用缓存

如果跳过这一步,所谓“semantic cache”很容易变成:

  • 把近似问题错当同一个问题

11.2 query normalization 最好记录版本

如果 rewrite 规则、术语映射或 query parser 变了,缓存也应该感知到。

所以 key 里通常还值得加上:

  • rewrite_version
  • parser_version
  • intent_schema_version

12. cache stampede 和并发放大要单独治理

很多团队一开始只看到:

  • 命中后很快

但生产里真正会把缓存拖垮的一类问题是:

  • 热点 key 同时过期
  • 大量并发一起 miss
  • 底层向量检索 / rerank 被瞬间打爆

这就是典型的 cache stampede

12.1 更稳的做法通常包括

  • 单飞机制或请求合并
  • 热点 key 提前刷新
  • soft TTL 配合后台重建
  • 对高成本 rerank 链路单独做并发保护

12.2 预热不只是“提前算一遍”

更成熟的预热通常还会考虑:

  • 哪些 query 真的稳定高频
  • 哪些 query 一旦 miss 会触发昂贵 rerank
  • 哪些 query 在工作日/高峰期明显更热

所以预热最好来自:

  • 高频 query 统计
  • 热门路由桶
  • 业务日历或活动窗口

13. 只看缓存命中率是不够的

很多团队会把命中率当成缓存策略唯一指标,这是不够的。

更应该一起观察:

  • hit rate
  • stale hit rate
  • permission leak rate
  • invalidation lag
  • p95 latency delta
  • cost saved
  • answer quality delta

因为高命中率不一定是好事,可能意味着:

  • 你命中了很多过期结果
  • 你把本该更新的知识长期固定住了

13.1 还值得单独看哪些观测字段

更像生产系统的缓存观察面板通常至少会带:

  • cache_hit
  • cache_layer
  • cache_key_hash
  • cache_age_ms
  • kb_version
  • rewrite_version
  • tenant_scope
  • permission_scope
  • stale_served
  • invalidation_reason

这样当结果出问题时,你才能回答:

  • 是哪一层命中的
  • 命中的是哪一版知识
  • 是不是 stale 结果
  • 是不是权限边界错了

13.2 缓存质量要进评测,而不是只进性能报表

更稳妥的做法通常是:

  • 对缓存命中样例单独抽样评测
  • 对 stale 命中样例单独跟踪质量变化
  • 把权限敏感 query 放进缓存专项回归

否则你只能知道:

  • 缓存帮你省了多少钱

却不知道:

  • 它是不是把旧答案更稳定地扩散给了更多人

14. 缓存进入治理闭环的方法

更稳妥的做法通常是:

  1. 观察高频 query 和高成本 query。
  2. 只选择重复高、变动低、风险低的场景先缓存。
  3. 记录缓存前后延迟、成本、质量变化。
  4. 对知识更新和权限收缩设置明确失效机制。
  5. 把异常命中样例回流到复盘和评测集。

这样缓存才是可控优化,而不是盲目加速。

14.1 更值得纳入发布门禁的缓存变更

下面这些变化更适合进入变更门禁,而不是直接上线试:

  • cache key 结构变化
  • TTL 策略变化
  • 权限维度是否进 key 的变化
  • stale 返回策略变化
  • negative cache 开关变化
  • evidence pack 缓存范围变化

原因很简单:

  • 这些改动看起来像“性能优化”
  • 实际上可能直接改变正确性和安全边界

15. 常见反模式

15.1 所有查询一律缓存

这通常会把高敏、高变动数据也一起缓存进去。

15.2 cache key 不包含权限和版本

这是最典型的生产事故来源之一。

15.3 知识更新后不主动失效

旧政策、旧 FAQ、旧配置说明会继续被稳定返回。

15.4 只看命中率,不看答案质量

可能缓存命中了,但最终回答更差了。

15.5 用缓存掩盖真实检索性能问题

如果底层检索本身质量差,缓存只是把坏结果更快返回。

15.6 把答案缓存和检索缓存混成一层

很多团队一上来就缓存最终答案,结果把:

  • 检索证据
  • 权限边界
  • Prompt 版本
  • 引用格式

全都缠在一起,最后很难失效和排障。

15.7 只做 TTL,不做事件失效

对热点公共 FAQ 还勉强能用,但对权限收缩、知识删除和政策更正通常不够。

16. 一个更稳的落地顺序

如果你准备把检索缓存做进生产,建议按这个顺序推进:

  1. 先找高频稳定 query,而不是全量缓存。
  2. 先做 query rewrite 缓存或 retrieval 候选缓存。
  3. 设计带权限、版本、策略版本的 cache key。
  4. 接入 TTL 和版本失效。
  5. 观察命中率、成本、延迟和质量变化。
  6. 稳定后再考虑 evidence pack 缓存和热点预热。
  7. 最后再评估 stale-while-revalidate、negative cache 和热点预热自动化。

这个顺序比“一上来缓存最终答案或全链路结果”更稳。

17. 推荐搭配阅读

18. 重点官方资源

以下资源已按 2026-07-09 的官方入口重新整理: