Chico Notes
LLM Wiki / RAG

RAG 缓存策略生产实战:多层缓存、权限隔离、版本失效与防击穿

从 Embedding、Retrieval、Rerank、Context、Answer、HTTP 到 Provider Prompt Cache,设计带版本、权限、新鲜度、失效事件、防击穿、可观测性与回滚能力的生产级 RAG 缓存体系。

持续修订的工程笔记

缓存通常被描述为最简单的性能优化:

先查 Cache
→ 命中就返回
→ Miss 再访问下游

但在 RAG 系统里,“同一个请求”很难定义。

下面任意一项不同,都可能让旧结果不可复用:

  • 用户租户、角色和 ACL;
  • Knowledge Base Scope;
  • Query Rewrite 和 Filter;
  • 语料 Revision;
  • Elasticsearch 物理索引;
  • Embedding 模型、维度和输入模板;
  • RRF、Reranker、Context Packer;
  • Prompt、生成模型和 Guardrail;
  • Citation Policy;
  • 当前时间与业务 as_of
  • Answer Mode;
  • Degraded 状态。

因此,RAG 缓存不是“把 Query 和 Answer 放进 Redis”,而是一套正确性协议:

定义等价请求
→ 冻结依赖版本
→ 隔离权限范围
→ 约束新鲜度
→ 处理并发 Miss
→ 传播失效事件
→ 记录命中来源
→ 支持回放和回滚

资料快照:本文核查截至 2026-08-06。HTTP 语义参考 RFC 9111;Cache-aside 与防击穿参考 Redis 官方文档;Elasticsearch 部分区分 Node Query Cache 与 Shard Request Cache;Prompt Cache 部分参考 OpenAI 官方资料中的 Exact Prefix 原则。具体 Provider 的命中窗口、计费和支持模型会变化,生产实现应读取真实 API Usage 字段并锁定 Provider / Model Version,而不是写死历史价格或 TTL。

RAG 缓存策略生产实战:从 Embedding、Retrieval、Rerank、Context 到 Answer 和 Prompt Cache 的多层缓存

图 1:每一层缓存不同对象,也拥有不同 Key、TTL、失效条件和安全边界。

一句话结论

生产级 RAG Cache 应遵循下面六个原则:

  1. 缓存 Key 表达完整依赖,而不是只表达用户问题。
  2. 版本变化产生 Natural Miss,避免把手工清库作为一致性机制。
  3. 任何私有结果都按 Tenant 与 Permission Scope 隔离。
  4. TTL 只是陈旧上限,关键变更仍需主动失效。
  5. Cache Miss 需要 Single-flight、Jitter 和受控 Stale,避免击穿。
  6. Cache Hit 也必须进入 Trace,才能复现答案来自哪次计算。

1. 先区分八类不同缓存

RAG 多层缓存架构:HTTP、Embedding、Retrieval、Rerank、Context、Answer 与 Provider Prompt Cache

图 2:缓存层级越靠后,结果越接近最终答案,但与权限、语料、Prompt、模型和业务时间的耦合也越强。

缓存层缓存对象主要收益主要风险
HTTP / Edge公开页面、静态资源、公共 API网络与渲染延迟私有内容被共享缓存
Embedding文本向量降低 Embedding 调用模型或模板变化仍复用旧向量
Query PlanRewrite、Intent、Filter Plan降低 Planner 成本会话、时间和权限语义丢失
RetrievalBM25 / kNN / RRF 候选降低 ES 与向量查询语料更新、Scope 越权、旧 Revision
RerankQuery–Candidate 分数和排序降低 GPU / API 成本Candidate 内容变化但 ID 不变
Context选中 Evidence 与组装结果降低回填、去重、压缩Token Budget、ACL、Citation 过期
Answer最终答案、Claim、Citation最大延迟与成本收益陈旧回答、权限泄漏、个性化错误
Provider Prompt推理端共享前缀计算降低输入处理成本和延迟Prompt 顺序变化导致 Miss;不可替代业务缓存

这八层不能使用同一个 Key Schema 和 TTL。

1.1 应用 Cache 与 Provider Prompt Cache 不同

应用 Cache 命中时:

不再调用模型

Provider Prompt Cache 命中时:

仍然调用模型
但复用相同 Prompt Prefix 的部分计算

Provider Prompt Cache 不会自动保证:

  • 语料新鲜度;
  • Answer 正确性;
  • 用户权限;
  • Citation 有效性;
  • 相同输出。

它只是推理层优化。


2. 请求等价性:缓存的真正定义

两个用户 Query 字符串相同,不代表两个请求等价。

“退款最晚什么时候申请?”

可能因为下面差异得到不同答案:

用户 A 可看内部政策
用户 B 只能看公开帮助中心

用户 A 问当前政策
用户 B 在历史审计中问 2025 年政策

Tenant A 使用政策 v3
Tenant B 仍使用定制政策 v2

可以把 Cache Equivalence 定义为:

Equivalent(Request A, Request B)
=
相同身份与权限语义
AND 相同 Query Plan
AND 相同语料状态
AND 相同 Pipeline
AND 相同时间语境
AND 相同输出策略

任何一项不同,都应 Miss 或进入不同 Namespace。


3. Versioned Cache Key:让版本变化自然失效

RAG 版本化 Cache Key:身份、Query、语料、Pipeline 和时间语境共同决定是否可复用

图 3:新 Prompt、新模型、新索引或新 ACL 应生成新 Key;旧 Key 可以自然过期,不必在每次发布时扫描整个 Redis。

推荐 Key 生成流程:

结构化依赖对象
→ Canonical JSON
→ SHA-256
→ namespace:schema_version:tenant:hash

示例:

{
  "cache_schema": "answer-cache-v6",
  "tenant_id": "tenant_001",
  "permission_scope_hash": "scope_7a9e",
  "normalized_query": "退款最晚什么时候申请",
  "query_plan_version": "query-plan-v8",
  "knowledge_base_ids": ["kb_policy"],
  "corpus_revision": "kb_policy@1942",
  "index_alias": "kb_chunks",
  "physical_indices": ["kb_chunks_v5"],
  "embedding_model_key": "embedding-x|template-v3",
  "retrieval_config": "retrieval-v12",
  "reranker_model_key": "reranker-y|input-v2",
  "context_packer_version": "context-v7",
  "answer_prompt_version": "answer-v15",
  "generation_model": "model-z",
  "citation_policy_version": "citation-v4",
  "confidence_policy_version": "confidence-v8",
  "business_as_of": "2026-08-06",
  "answer_mode": "grounded_concise"
}

最终 Key:

rag:answer:v6:tenant_001:8d6f2f1a...

3.1 不要把明文 Query 和 ACL 放进 Key

Redis Key 会出现在:

  • Metrics;
  • Slow Log;
  • 运维工具;
  • Debug 输出;
  • 内存分析。

因此建议只保留低敏感 Namespace 和 Hash。

3.2 Key Schema 自身要版本化

当 Key 逻辑变化时:

v5 → v6

新请求自然 Miss。不要让新旧代码对同一 Key 有不同解释。


4. Embedding Cache

Embedding 是最适合缓存的 RAG 对象之一,因为它通常是确定性的、输入较稳定、复用率高。

4.1 Key 必须包含什么

{
  "normalized_text_sha256": "...",
  "model_provider": "provider-a",
  "model_id": "embedding-model-x",
  "model_revision": "2026-07-18",
  "input_type": "query",
  "input_template_version": "embed-query-v3",
  "dimension": 1024,
  "normalization": "unicode-nfc-whitespace-v2"
}

只使用 text_hash + model_name 仍然可能错误,因为:

  • 同一模型支持 Query / Document 不同前缀;
  • 模型服务可能在相同名称下升级;
  • 维度或归一化方式可能变化;
  • 文本预处理变化会影响向量。

4.2 Query Embedding 与 Document Embedding 分开

query: "query: ..."
document: "passage: ..."

即使原始文本相同,输入角色不同也不能复用。

4.3 跨租户复用是否安全

对公开、去标识化文本,Embedding 可以按内容 Hash 跨租户复用。

对可能包含:

  • PII;
  • 私有代码;
  • 企业文档;
  • 用户查询;

建议:

  • Tenant-scoped Key;
  • 加密 Value;
  • 不记录明文;
  • 设置 Retention;
  • 对 ZDR / 合规模式单独 Namespace 或完全 No-store。

4.4 Value 应带 Manifest

{
  "vector": [0.012, -0.041],
  "dimension": 1024,
  "model_key": "embedding-model-x@2026-07-18",
  "template_version": "embed-query-v3",
  "text_sha256": "...",
  "created_at": "2026-08-06T10:00:00Z"
}

读取时校验 Manifest,避免错误反序列化或维度污染。


5. Query Plan Cache

可以缓存:

  • Query Classification;
  • Standalone Rewrite;
  • Entity Resolution;
  • Lexical Expansion;
  • Subquery Decomposition;
  • Router Decision。

5.1 Key 不能只有当前问题

Follow-up Query:

“那第二个方案呢?”

依赖会话历史,因此 Key 应包含:

conversation_state_hash
summary_version
visible_history_hash

5.2 权限 Scope 不应由缓存的 Planner 决定

可以缓存:

模型提出的逻辑 Filter Intent

不能缓存并跨用户复用:

最终 Tenant / ACL Filter

最终 Scope 必须由当前请求的权限系统重新编译。

5.3 时间表达需要时间基准

“最近一周”
“现在有效的政策”
“昨天的版本”

Key 必须包含 business_as_of 或解析后的绝对时间范围。


6. Retrieval Cache

Retrieval Cache 保存候选结果,而不是最终答案。

6.1 推荐缓存 Candidate Manifest

{
  "query_plan_hash": "...",
  "scope_hash": "...",
  "corpus_revision": "kb_policy@1942",
  "physical_indices": ["kb_chunks_v5"],
  "retrieval_config": "retrieval-v12",
  "candidates": [
    {
      "chunk_id": "chunk_001",
      "source_revision_id": "policy@rev_43",
      "bm25_rank": 1,
      "dense_rank": 3,
      "rrf_rank": 1,
      "content_hash": "..."
    }
  ],
  "partial": false,
  "created_at": "2026-08-06T10:00:00Z"
}

6.2 Corpus Revision 是关键

TTL 不能知道哪些文档发生变化。

推荐每个检索 Scope 有稳定 Revision:

KB revision
Index generation
Source snapshot set

文档更新后:

kb_policy@1942 → kb_policy@1943

新请求自然 Miss。

6.3 大 KB 的 Revision 粒度

全局 Revision 简单但会导致大量 Miss。

可以分层:

tenant_revision
knowledge_base_revision
source_group_revision
entity_revision

Key 只包含当前 Scope 涉及的 Revision。

但 Revision 越细,复杂度越高。早期优先使用 KB 级 Revision。

6.4 Retrieval Cache 必须包含 Scope

至少包含:

  • Tenant;
  • KB IDs;
  • ACL / Group Hash;
  • Language;
  • Document Status;
  • Time Filter;
  • Source Type;
  • Answer Mode。

权限相关结果绝不能只按 Query 共享。


7. Elasticsearch 自带缓存不是应用 Retrieval Cache

7.1 Node Query Cache

Elasticsearch Query Cache 主要缓存 Filter Context 中的查询结果:

  • 每个 Node 一个 Cache;
  • 多个 Shard 共享;
  • LRU 驱逐;
  • 按 Segment 缓存;
  • Segment Merge 可能使结果失效;
  • 普通 Term Query 和非 Filter Context 不一定可缓存。

它优化的是底层 Filter Bitset,不会替应用保存完整的多路候选、RRF、Rerank 和 Trace。

7.2 Shard Request Cache

Shard Request Cache 缓存每个 Shard 的本地 Search Result。

官方文档说明:

  • 默认主要缓存 size=0 请求的 Aggregation、Total 和 Suggestion;
  • 使用 now 的多数查询不可缓存;
  • 非确定性 Script 不可缓存;
  • Shard Refresh 或 Mapping 更新会自动失效;
  • 整个 JSON Body 的 Hash 参与 Key;
  • JSON 字段顺序不同可能产生不同 Key。

因此,应用应 Canonicalize JSON:

{
  "aggs": {...},
  "query": {...},
  "size": 0
}

而不是每次随机序列化字段顺序。

7.3 三层关系

Elasticsearch Query Cache
→ 底层 Filter / Segment 优化

Elasticsearch Shard Request Cache
→ Shard Search Response 优化

Application Retrieval Cache
→ 完整 Query Plan、Scope、候选、版本和 Trace 复用

三者可以共存,但监控指标必须分开。


8. Rerank Cache

Rerank 输入通常是:

Query × N 个 Candidate Text

成本与 Candidate 数和 Token 数成正比,适合缓存。

8.1 Key 设计

{
  "query_hash": "...",
  "candidate_manifest_hash": "...",
  "reranker_model": "reranker-y",
  "model_revision": "2026-06-01",
  "input_template": "rerank-v4",
  "max_length": 1024,
  "truncation_policy": "head-tail-v2"
}

8.2 Candidate ID 不够

如果 Chunk 内容改变但 ID 没变,旧 Rerank Score 会污染结果。

Candidate Manifest Hash 应包含:

candidate_id
source_revision
content_hash
input_text_hash

8.3 缓存单 Pair 还是整批

单 Pair Cache

query_hash + candidate_hash + model
→ score

优点:候选组合变化时仍能复用。

缺点:Key 数量大,网络 Round Trip 多。

Batch Cache

query_hash + ordered_candidate_manifest_hash
→ ranked list

优点:读取简单。

缺点:候选稍变就整体 Miss。

生产中可以:

L1 in-process Pair Cache
+
L2 Redis Batch Cache

9. Context Cache

Context Cache 保存经过:

Hydrate
→ Parent / Neighbor Expansion
→ Dedup
→ MMR
→ Compression
→ Token Budget
→ Citation Mapping

后的 Context Manifest。

9.1 Key 必须包含

ordered candidate manifest
context packer version
token budget
citation policy
source revision
permission scope
language
model context limit

9.2 不要只缓存拼接文本

错误 Value:

一大段 Context String

推荐:

{
  "context_manifest_id": "ctx_001",
  "blocks": [
    {
      "citation_key": "E1",
      "evidence_id": "evidence_2031",
      "source_revision_id": "policy@rev_43",
      "content_hash": "...",
      "context_order": 1,
      "token_count": 120,
      "truncated": false
    }
  ],
  "packer_version": "context-v7",
  "token_budget": 6000
}

这样才能验证 Citation、Revision 和权限。


10. Answer Cache

Answer Cache 收益最大,风险也最大。

10.1 什么时候可以缓存

  • Grounded Fact Answer;
  • Query 与 Scope 稳定;
  • Source Revision 固定;
  • Citation Validation 通过;
  • 没有用户个性化;
  • 没有高敏感信息;
  • 没有快速变化数据;
  • Answer Policy 允许。

10.2 Key 必须包含

normalized query
context manifest hash
prompt version
model / reasoning configuration
answer mode
citation policy
confidence policy
permission scope
business as-of
locale

10.3 Value 保存什么

{
  "answer": "...",
  "claims": [...],
  "citations": [...],
  "answer_status": "verified",
  "context_manifest_id": "ctx_001",
  "source_revisions": ["policy@rev_43"],
  "prompt_version": "answer-v15",
  "model": "model-z",
  "created_at": "2026-08-06T10:00:00Z",
  "soft_expire_at": "2026-08-06T10:10:00Z",
  "hard_expire_at": "2026-08-06T11:00:00Z"
}

10.4 不建议缓存最终答案的场景

  • 医疗、法律、财务高风险建议;
  • 个性化身份信息;
  • 实时价格、库存、天气、比赛和运营状态;
  • Tool 执行结果;
  • 权限频繁变化;
  • Evidence 冲突未解决;
  • Citation 未验证;
  • Degraded / Partial Answer;
  • 用户要求最新状态。

可以只缓存 Retrieval / Context,不缓存 Answer。


11. Semantic Answer Cache

Semantic Cache 使用 Query Embedding 找相似历史问题:

新 Query
→ 与历史 Query 向量近邻匹配
→ 超过阈值则复用答案

它比 Exact Cache 风险高得多。

11.1 语义相似不等价

“员工可以报销出租车吗?”
“员工不可以报销出租车吗?”

Embedding 可能非常接近,但逻辑相反。

“2025 年政策是什么?”
“当前政策是什么?”

主题相同,时间语义不同。

11.2 必须先通过 Constraint Equivalence

至少比较:

  • 实体;
  • 否定;
  • 时间;
  • 数值;
  • 地域;
  • Tenant / ACL;
  • Query Intent;
  • Required Facts;
  • Answer Mode。

11.3 推荐只复用中间结果

更安全的方式:

相似 Query
→ 复用 Query Plan / Candidate Seeds
→ 重新做权限过滤、检索校验和生成

而不是直接返回旧答案。

11.4 Semantic Cache 需要独立评测

指标:

semantic_cache_hit_rate
semantic_false_hit_rate
constraint_mismatch_rate
stale_semantic_hit_rate
unauthorized_semantic_hit_rate
quality_delta_on_hits

False Hit 必须作为 Hard Gate。


12. Provider Prompt Cache

OpenAI 官方资料说明,Prompt Cache 依赖共享的精确 Prefix;为了提高命中率,应将稳定指令和示例放在前面,将用户特定、请求特定内容放在后面。

12.1 推荐 Prompt 顺序

System / Developer Instructions
稳定工具定义
稳定输出 Schema
稳定 Few-shot Examples
稳定领域规则
---------------- Shared Prefix ----------------
Tenant / User Scope
Current Context
User Query
Dynamic Tool Results

12.2 会造成 Miss 的变化

  • Model 改变;
  • System / Developer Prompt 改变;
  • Tool 列表或顺序改变;
  • JSON Schema 字段顺序改变;
  • 图片或文件改变;
  • 静态示例中插入动态时间;
  • MCP Tool 动态顺序不稳定;
  • Sandbox / Execution Context 改变。

12.3 Canonicalize Tool 和 Schema

按稳定名称排序工具
固定 JSON 字段顺序
不要在静态 Prefix 中写 request_id / timestamp
Prompt Template 必须版本化

12.4 监控真实 Usage

不要根据“Prompt 看起来一样”推断命中。

记录 Provider 返回的:

input_tokens
cached_input_tokens
cache_write_tokens(如 Provider 提供)
provider
model
prompt_prefix_hash

12.5 Prompt Cache 不等于数据缓存

Prompt Cache 仍然可能:

  • 重新生成不同答案;
  • 使用过时 Context;
  • 产生错误 Citation;
  • 受到动态内容影响。

它不能替代 Source Revision 和 Answer Cache Key。


13. HTTP / Edge Cache

RFC 9111 区分:

private cache
→ 单一用户专用

shared cache
→ 多用户共享的中间缓存

RAG 私有答案默认不应进入 Shared Cache。

13.1 推荐响应头

私有、可短期复用:

Cache-Control: private, max-age=60, must-revalidate
Vary: Authorization, Accept-Language
ETag: "answer-manifest-hash"

敏感、不可缓存:

Cache-Control: no-store

注意:RFC 9111 也提醒,no-store 不是完整的隐私安全机制;仍需 TLS、服务端控制和数据治理。

13.2 Vary 不是万能权限隔离

CDN 对 Authorization、Cookie 和自定义 Header 的行为不同。高敏感答案应绕过 Shared CDN Cache,而不是依赖复杂 Vary 组合。

13.3 ETag 与 Revalidation

对于公开知识页和 Citation Preview:

ETag = content_hash / revision_hash

客户端可以:

If-None-Match: "..."

Source Revision 变化后返回新内容。


14. TTL:它是陈旧上限,不是一致性保证

Redis 官方 Cache-aside 文档强调,TTL 决定缓存可能陈旧的最长窗口;更短 TTL 降低陈旧风险,但会降低 Hit Rate、增加下游负载。

14.1 按对象设计 TTL

对象TTL 示例失效主因
Query Embedding天 / 周模型或模板版本
Document Embedding长期Source Revision / Model
Query Plan分钟 / 小时会话、时间和 Prompt
Retrieval秒 / 分钟Corpus / ACL Revision
Rerank分钟 / 小时Candidate / Model
Context秒 / 分钟Revision、Policy、Budget
Answer秒 / 分钟Freshness、Scope、Prompt
Negative Cache很短新数据创建
Citation Preview分钟 / 小时Source Revision / ACL

这些只是示例。

14.2 Soft TTL 与 Hard TTL

Soft TTL
→ 超过后结果可以短暂使用
→ 同时后台刷新

Hard TTL
→ 超过后绝不能复用

适合:

{
  "soft_expire_at": "2026-08-06T10:10:00Z",
  "hard_expire_at": "2026-08-06T11:00:00Z"
}

高风险和实时数据通常不允许 Stale。

14.3 TTL Jitter

base_ttl = 600s
jitter = random(-60s, +60s)

避免大量 Key 同时过期。


15. 主动失效:写入成功后应该发生什么

Cache-aside 常见写路径:

写 Source of Truth
→ Commit
→ 发布 Invalidation Event
→ 删除或推进 Revision
→ 下一次读取重新填充

Redis 官方文档推荐在更新 Primary 后删除 Cache Key,让下一次读从 Primary 重新填充,而不是在应用层尝试双写保持完全同步。

15.1 RAG 失效事件

{
  "event_id": "evt_001",
  "event_type": "knowledge_revision_published",
  "tenant_id": "tenant_001",
  "knowledge_base_id": "kb_policy",
  "knowledge_id": "policy_refund",
  "old_revision": 42,
  "new_revision": 43,
  "affected_namespaces": [
    "retrieval",
    "context",
    "answer",
    "citation_preview"
  ],
  "committed_at": "2026-08-06T10:00:00Z"
}

15.2 优先推进 Revision,而不是枚举所有 Key

如果 Key 包含 kb_revision=43

新请求自动使用新 Key

旧 Key 留待 TTL / LRU 清理。

事件仍然有价值:

  • 主动删除高价值旧答案;
  • 预热新版本;
  • 更新 Dashboard;
  • 防止内存浪费;
  • 触发回归测试。

16. Cache Stampede、Penetration 与 Avalanche

Cache Stampede 与一致性控制:Single-flight、Soft / Hard TTL、Jitter 和 Negative Cache

图 4:热门 Key 过期后只能有一个 Loader 访问昂贵下游,其余请求等待、读取受控 Stale 或快速降级。

16.1 Stampede / Breakdown

热门 Key 同时 Miss:

1000 个请求
→ 同时查询 ES
→ 同时调用 Reranker
→ 同时调用 LLM

防护:

  • Single-flight;
  • 分布式锁;
  • Soft TTL;
  • Probabilistic Early Refresh;
  • 预热;
  • 请求合并;
  • 下游限流。

16.2 Cache Penetration

不断请求不存在对象:

每次 Miss
→ 每次打 Primary

可使用 Negative Cache:

{
  "status": "not_found",
  "source_revision": 43,
  "expires_in": 30
}

Negative TTL 应显著短于正向结果;对象创建后必须主动失效。

16.3 Cache Avalanche

大量 Key 同时过期或 Redis 故障:

  • TTL Jitter;
  • 多级 Cache;
  • 限流;
  • Circuit Breaker;
  • 下游容量保护;
  • Stale-if-error;
  • 批量预热;
  • Redis 高可用。

17. Single-flight 正确实现

17.1 锁需要唯一 Token

SET lock:key token NX PX 5000

释放时只能删除自己的锁:

if redis.call('GET', KEYS[1]) == ARGV[1] then
  return redis.call('DEL', KEYS[1])
end
return 0

避免 A 的锁过期后 B 获得新锁,A 又误删 B 的锁。

17.2 Lock TTL 必须覆盖 Loader 上限

lock_ttl > downstream_deadline + publish_margin

如果 Loader 可能超过 TTL,需要续租或 Fencing Token。

17.3 等待者策略

短暂等待 Cache 填充
→ 超时后读取 Stale
→ 没有 Stale 则快速降级

不要让所有等待者最终都回源。

17.4 成功、失败和 Partial 分 Namespace

rag:answer:success:...
rag:answer:partial:...
rag:answer:error:...

失败结果不能覆盖成功结果。


18. Stale-While-Revalidate 与 Stale-if-Error

18.1 Stale-While-Revalidate

Soft TTL 已过
Hard TTL 未过
→ 返回旧值
→ 后台单飞刷新

适合:

  • 低风险公共知识;
  • 允许几分钟陈旧;
  • 下游昂贵;
  • 高并发。

18.2 Stale-if-Error

下游故障时使用上次已验证结果:

{
  "cache_status": "stale_if_error",
  "age_seconds": 420,
  "source_revision": 42,
  "warning": "当前检索服务不可用,答案来自最近一次已验证结果。"
}

18.3 不允许 Stale 的场景

  • ACL 已撤销;
  • Source 被删除;
  • Critical Policy 更新;
  • 安全修复;
  • 实时金融 / 医疗 / 法律状态;
  • 用户明确要求当前状态;
  • 旧 Answer 使用已失效 Citation。

需要支持:

hard_invalidation
→ 即使下游错误也不能返回旧值

19. 多租户与权限隔离

19.1 最低要求

任何含私有内容的 Key 必须包含:

tenant_id
permission_scope_hash
policy_version

19.2 User ID 不是完整 Scope

同一用户的角色和群组可能变化。

permission_scope_hash 可以基于:

tenant
roles
groups
resource grants
policy version

Canonicalize 后 Hash。

19.3 共享结果的安全策略

允许跨用户共享之前证明:

same tenant
AND same effective scope
AND no user-specific context
AND no PII
AND same answer policy

19.4 Revocation

权限撤销需要:

  • 推进 permission_policy_version
  • 主动删除高风险 Answer / Context Cache;
  • 撤销 Citation Preview Token;
  • 记录 Audit Event。

仅依赖 TTL 可能泄漏撤权后的旧结果。

19.5 加密与 Redis 隔离

高敏感场景:

  • Value 加密;
  • Tenant 独立 Prefix / Database / Cluster;
  • Redis ACL;
  • TLS;
  • 内存和 Snapshot 治理;
  • Key 不包含 PII;
  • Operator Access Audit。

20. Cache Key 的 Go 实现

package cachekey

import (
    "crypto/sha256"
    "encoding/hex"
    "encoding/json"
    "fmt"
    "sort"
)

type AnswerKeyInput struct {
    SchemaVersion       string   `json:"schema_version"`
    TenantID           string   `json:"tenant_id"`
    PermissionScope    string   `json:"permission_scope_hash"`
    NormalizedQuery    string   `json:"normalized_query"`
    KnowledgeBaseIDs   []string `json:"knowledge_base_ids"`
    CorpusRevision     string   `json:"corpus_revision"`
    QueryPlanVersion   string   `json:"query_plan_version"`
    RetrievalVersion   string   `json:"retrieval_version"`
    RerankerVersion    string   `json:"reranker_version"`
    ContextVersion     string   `json:"context_version"`
    PromptVersion      string   `json:"prompt_version"`
    ModelKey           string   `json:"model_key"`
    CitationPolicy     string   `json:"citation_policy"`
    ConfidencePolicy   string   `json:"confidence_policy"`
    BusinessAsOf       string   `json:"business_as_of"`
    AnswerMode         string   `json:"answer_mode"`
}

func BuildAnswerKey(in AnswerKeyInput) (string, error) {
    if in.SchemaVersion == "" || in.TenantID == "" {
        return "", fmt.Errorf("schema version and tenant are required")
    }

    ids := append([]string(nil), in.KnowledgeBaseIDs...)
    sort.Strings(ids)
    in.KnowledgeBaseIDs = ids

    payload, err := json.Marshal(in)
    if err != nil {
        return "", fmt.Errorf("marshal cache key input: %w", err)
    }

    sum := sha256.Sum256(payload)
    return fmt.Sprintf(
        "rag:answer:%s:%s:%s",
        in.SchemaVersion,
        in.TenantID,
        hex.EncodeToString(sum[:]),
    ), nil
}

生产中应使用稳定 Canonical JSON Library,并为 Key Builder 编写 Golden Test。


21. Cache-aside 读取流程

21.1 Cache Failure 不应拖垮主链路

Redis 超时:

短 Deadline
→ Cache Bypass
→ 保护下游容量
→ 记录 cache_backend_error

缓存是优化层,不应该成为单点故障;但 Cache 大面积失效时必须有限流,否则所有流量会打穿下游。


22. 数据模型与 Cache Manifest

CREATE TABLE cache_namespaces (
  namespace_id       VARCHAR(64) PRIMARY KEY,
  cache_type         VARCHAR(32) NOT NULL,
  schema_version     VARCHAR(32) NOT NULL,
  default_ttl_sec    INT NOT NULL,
  max_value_bytes    INT NOT NULL,
  sensitivity_class  VARCHAR(32) NOT NULL,
  created_at         DATETIME(6) NOT NULL
);

CREATE TABLE cache_invalidation_events (
  event_id            VARCHAR(64) PRIMARY KEY,
  tenant_id           VARCHAR(64) NOT NULL,
  event_type          VARCHAR(64) NOT NULL,
  resource_type       VARCHAR(32) NOT NULL,
  resource_id         VARCHAR(128) NOT NULL,
  old_revision        VARCHAR(128),
  new_revision        VARCHAR(128),
  affected_namespaces JSON NOT NULL,
  status              VARCHAR(24) NOT NULL,
  created_at          DATETIME(6) NOT NULL,
  processed_at        DATETIME(6)
);

CREATE TABLE cache_audit_samples (
  sample_id          VARCHAR(64) PRIMARY KEY,
  request_id         VARCHAR(64) NOT NULL,
  namespace_id       VARCHAR(64) NOT NULL,
  cache_key_hash     CHAR(64) NOT NULL,
  cache_status       VARCHAR(32) NOT NULL,
  value_manifest     JSON,
  age_ms             BIGINT,
  stale              BOOLEAN NOT NULL,
  created_at         DATETIME(6) NOT NULL
);

不需要把每次 Cache Read 都写进 MySQL;可以采样、聚合或写入 Trace Backend。


23. 可观测性

每次 Cache Span 至少记录:

cache.system
cache.operation
cache.namespace
cache.key_hash
cache.hit
cache.status
cache.age_ms
cache.ttl_remaining_ms
cache.value_size_bytes
cache.schema_version
cache.source_revision
cache.scope_hash
cache.stale
cache.refresh_triggered
cache.singleflight_role
cache.error.type

23.1 RAG Trace 中的位置

Hit 时也要保留逻辑阶段,否则 Trace 会误以为 Retrieval / Generation 没有发生。

23.2 指标

hit_rate
miss_rate
stale_hit_rate
bypass_rate
eviction_rate
load_latency
cache_latency
singleflight_waiters
stampede_prevented
negative_hit_rate
false_hit_rate
invalidation_lag
prompt_cached_token_ratio
cost_saved

23.3 Hit Rate 不是唯一目标

高 Hit Rate 可能来自:

  • TTL 太长;
  • Key 缺少版本;
  • 权限范围过度共享;
  • Semantic Threshold 太宽;
  • 旧答案重复使用。

必须同时监控:

stale_answer_rate
unauthorized_cache_hit_rate
cache_related_bad_case_rate
quality_delta_on_cache_hits

24. 成本与容量模型

24.1 每层价值

Saved Cost
=
Hit Count
× Avoided Downstream Cost
-
Cache Storage
-
Network
-
Serialization
-
Invalidation
-
Operational Cost

Embedding Cache 的单次节省小但复用高;Answer Cache 单次节省大但风险高。

24.2 Value Size

不要把巨大 Candidate / Context / Answer 无限放进 Redis。

建议:

  • 设置 max_value_bytes
  • 大对象存对象存储,Redis 只存 Manifest URI;
  • 压缩前先评估 CPU;
  • 对热点大对象做 Local L1;
  • 禁止无界 JSON。

24.3 Memory Budget

memory ≈ unique_keys × avg_value_size × replication_factor × overhead

还要考虑:

  • Redis Object Overhead;
  • Fragmentation;
  • Replica;
  • Persistence;
  • Failover Buffer;
  • Hot Key Distribution。

24.4 Eviction Policy

不同 Namespace 最好不同实例或不同预算:

Embedding
→ 长寿命、价值稳定

Answer
→ 短寿命、权限敏感

Rate Limit / Session
→ 不能被 Answer Cache 挤出

不要把所有用途放在同一个无边界 Redis 中。


25. 预热与发布

25.1 什么时候预热

  • 新索引 Alias 切换;
  • 新 Embedding / Reranker;
  • 大促或发布会;
  • 高频 FAQ;
  • 已知热门 Query;
  • Disaster Recovery 切换。

25.2 预热不能绕过权限

预热 Retrieval / Answer 时使用明确的 Scope Cohort:

public
employee
support
admin

不能用超级管理员结果服务普通用户。

25.3 版本发布顺序

发布新索引 / Pipeline
→ 生成新 Cache Namespace
→ 影子流量预热
→ 验证命中质量
→ Canary
→ 全量
→ 旧 Namespace 保留回滚窗口
→ 再自然清理

25.4 回滚

回滚不需要恢复旧 Key:

切回旧 Pipeline Version
→ 自动重新命中旧 Namespace

前提是旧 Cache 尚未被主动删除。


26. 测试策略

26.1 Key Golden Tests

固定输入应产生固定 Hash。

覆盖:

  • KB IDs 顺序不同;
  • JSON 字段顺序不同;
  • Scope 变化;
  • Revision 变化;
  • Prompt 变化;
  • as_of 变化;
  • Unicode 与空白规范化。

26.2 Security Tests

用户 A 写入
→ 用户 B 不能命中

ACL 撤销
→ 旧 Answer / Context 不能继续返回

Tenant 相同但 Group 不同
→ Scope Hash 不同

26.3 Invalidation Tests

  • Source 更新;
  • 删除;
  • 权限变化;
  • Index Alias 切换;
  • Prompt 升级;
  • Model 升级;
  • Citation Policy 升级;
  • Confidence Policy 升级。

26.4 Stampede Tests

对一个冷 Key 并发 100 / 1000 请求:

loader_count ≈ 1
waiter_timeout 在预算内
下游没有突发放大

26.5 Chaos Tests

  • Redis Timeout;
  • Redis Failover;
  • Pub/Sub / Event 延迟;
  • Loader 超过 Lock TTL;
  • Partial Write;
  • Object Storage 不可用;
  • Clock Skew。

27. Release Gate

rag_cache_gate_version: cache-gate-v5

hard_gates:
  cross_tenant_cache_hit_rate:
    max: 0
  unauthorized_cache_hit_rate:
    max: 0
  stale_critical_answer_rate:
    max: 0
  semantic_false_hit_rate:
    max: 0
  cache_manifest_validation_rate:
    min: 1.0

quality_gates:
  answer_quality_delta_on_hits:
    min: -0.005
  citation_validity_on_hits:
    min: 0.995
  cache_related_bad_case_rate:
    max: 0.001

performance_gates:
  cache_p95_ms:
    max: 10
  singleflight_loader_amplification:
    max: 1.05
  invalidation_p95_ms:
    max: 5000

cost_gates:
  prompt_cached_token_ratio:
    min: 0.50
  downstream_call_reduction:
    min: 0.20

这些阈值是示例。


28. 常见反模式

  1. Answer Cache Key 只有 Query。
  2. 用 User ID 代替 Effective Permission Scope。
  3. 只靠 TTL,不处理 Source / ACL 更新。
  4. 每次发布 FLUSHALL
  5. Document 内容变了,但 Chunk ID 不变,Rerank Cache 仍命中。
  6. Context Cache 只存 String,不存 Evidence Manifest。
  7. Semantic Cache 只看 cosine threshold。
  8. 高风险答案进入 Shared CDN。
  9. no-store 被当成全部隐私方案。
  10. Prompt Prefix 中包含动态时间和 Request ID。
  11. Tool 定义顺序随机,导致 Prompt Cache Miss。
  12. 缓存 Degraded Answer 覆盖 Verified Answer。
  13. 热门 Key 过期没有 Single-flight。
  14. Lock 无唯一 Token,误删他人锁。
  15. Negative Cache TTL 与正向 Cache 一样长。
  16. Cache Backend 故障时所有请求直接打下游。
  17. 只看 Hit Rate,不看 Stale / Unauthorized / False Hit。
  18. Cache Hit 不进 Trace,Bad Case 无法复现。
  19. 所有 Namespace 共用一个 Redis 内存预算。
  20. 缓存值没有 Schema / Model / Revision Manifest。

29. P0 / P1 / P2 落地路线

P0:安全的精确缓存

  • Embedding Cache;
  • Query Plan Cache;
  • Retrieval Cache;
  • Versioned Key;
  • Tenant / Scope Hash;
  • Cache-aside;
  • TTL + Invalidation;
  • Single-flight;
  • Negative Cache;
  • Cache Trace;
  • 禁止高风险 Answer Cache。

验收标准:

任何 Cache Hit 都能证明:
当前请求与原计算在权限、语料、Pipeline 和时间语境上等价。

P1:Context / Answer 与发布治理

  • Context Manifest Cache;
  • Verified Answer Cache;
  • Soft / Hard TTL;
  • Stale-while-revalidate;
  • Stale-if-error;
  • Event-driven Invalidation;
  • Prompt Prefix 优化;
  • Cache Quality Gate;
  • Shadow Prewarm;
  • Namespace Rollback。

P2:高级缓存

  • Semantic Cache;
  • Learned TTL;
  • Probabilistic Early Refresh;
  • Cost-aware Admission;
  • Multi-region Cache;
  • Tenant-specific Policy;
  • Adaptive L1 / L2;
  • Popularity Forecast;
  • Cache-aware Query Planner;
  • Cross-request Rerank Pair Cache;
  • Automatic Prompt Prefix Optimization。

30. 上线检查清单

  • 是否列出所有 Cache Namespace?
  • 每一层是否定义缓存对象和正确性边界?
  • Cache Key 是否包含 Schema Version?
  • 是否使用 Canonical JSON?
  • Key 是否避免明文 Query、PII 和 ACL?
  • Tenant 是否进入 Key?
  • Effective Permission Scope 是否进入 Key?
  • Permission Policy Version 是否进入 Key?
  • Corpus / KB Revision 是否进入 Retrieval Key?
  • Physical Index / Alias Generation 是否可追踪?
  • Embedding Model、Revision、Input Type 和 Template 是否进入 Key?
  • Rerank Candidate 是否包含 Content Hash?
  • Context Cache 是否保存 Evidence Manifest?
  • Answer Cache 是否包含 Prompt、Model、Citation 和 Confidence Policy?
  • Business as_of 是否进入时间敏感 Key?
  • Answer Mode 和 Locale 是否进入 Key?
  • 是否区分 Query Cache、Shard Request Cache 和应用 Retrieval Cache?
  • ES JSON 是否稳定序列化?
  • 是否定义 Soft TTL 和 Hard TTL?
  • TTL 是否匹配业务陈旧容忍度?
  • 是否加入 TTL Jitter?
  • 写入 Source of Truth 后是否发布 Invalidation Event?
  • 是否优先推进 Revision 而不是扫描所有 Key?
  • ACL 撤销是否执行 Hard Invalidation?
  • 热门 Key 是否有 Single-flight?
  • Lock 是否有唯一 Token 和合理 TTL?
  • 等待者是否有超时和 Stale 策略?
  • Negative Cache 是否使用短 TTL?
  • 新对象创建是否使 Negative Cache 失效?
  • Redis 故障是否有 Bypass、限流和下游保护?
  • Verified、Partial、Degraded、Error 是否分 Namespace?
  • Semantic Cache 是否先验证 Constraint Equivalence?
  • Semantic False Hit 是否是 Hard Gate?
  • 私有 Answer 是否绕过 Shared CDN?
  • HTTP privateno-storeVary 和 ETag 是否正确?
  • Prompt 静态 Prefix 是否在前?
  • Tool 与 JSON Schema 顺序是否稳定?
  • 是否记录 Provider 返回的 Cached Token Usage?
  • Cache Hit 是否保留原始 Manifest / Trace Link?
  • 是否监控 Hit、Stale、False Hit、Unauthorized 和 Invalidation Lag?
  • 是否为不同 Namespace 分配独立内存预算?
  • 是否有预热、Canary、回滚和旧 Namespace 保留窗口?
  • 是否做 Stampede、Security 和 Chaos Test?

总结

RAG Cache 的工程价值不是“少调一次模型”,而是在不改变请求语义的前提下安全复用已有计算:

Embedding Cache
→ 复用稳定向量

Retrieval Cache
→ 复用同 Scope、同语料版本的候选

Rerank Cache
→ 复用同 Query–Candidate–Model 评分

Context Cache
→ 复用同 Evidence、同 Token Budget 的组装结果

Answer Cache
→ 复用同权限、同 Revision、同 Prompt 和 Policy 的已验证答案

Prompt Cache
→ 复用 Provider 内部的相同 Prefix 计算

最重要的工程原则是:

缓存 Key 必须证明两个请求等价;Revision 必须让变化自然产生 Miss;权限必须阻止不该共享的结果;Trace 必须让每次命中仍然可复现。

做到这些,缓存才真正降低成本和延迟;否则它只是把旧错误、旧权限和旧答案更快地交付给更多用户。

官方资料与相关阅读

讨论

继续讨论这篇笔记

有问题、补充案例或不同观点,可以通过 GitHub Discussions 继续交流。

On this page

一句话结论1. 先区分八类不同缓存1.1 应用 Cache 与 Provider Prompt Cache 不同2. 请求等价性:缓存的真正定义3. Versioned Cache Key:让版本变化自然失效3.1 不要把明文 Query 和 ACL 放进 Key3.2 Key Schema 自身要版本化4. Embedding Cache4.1 Key 必须包含什么4.2 Query Embedding 与 Document Embedding 分开4.3 跨租户复用是否安全4.4 Value 应带 Manifest5. Query Plan Cache5.1 Key 不能只有当前问题5.2 权限 Scope 不应由缓存的 Planner 决定5.3 时间表达需要时间基准6. Retrieval Cache6.1 推荐缓存 Candidate Manifest6.2 Corpus Revision 是关键6.3 大 KB 的 Revision 粒度6.4 Retrieval Cache 必须包含 Scope7. Elasticsearch 自带缓存不是应用 Retrieval Cache7.1 Node Query Cache7.2 Shard Request Cache7.3 三层关系8. Rerank Cache8.1 Key 设计8.2 Candidate ID 不够8.3 缓存单 Pair 还是整批单 Pair CacheBatch Cache9. Context Cache9.1 Key 必须包含9.2 不要只缓存拼接文本10. Answer Cache10.1 什么时候可以缓存10.2 Key 必须包含10.3 Value 保存什么10.4 不建议缓存最终答案的场景11. Semantic Answer Cache11.1 语义相似不等价11.2 必须先通过 Constraint Equivalence11.3 推荐只复用中间结果11.4 Semantic Cache 需要独立评测12. Provider Prompt Cache12.1 推荐 Prompt 顺序12.2 会造成 Miss 的变化12.3 Canonicalize Tool 和 Schema12.4 监控真实 Usage12.5 Prompt Cache 不等于数据缓存13. HTTP / Edge Cache13.1 推荐响应头13.2 Vary 不是万能权限隔离13.3 ETag 与 Revalidation14. TTL:它是陈旧上限,不是一致性保证14.1 按对象设计 TTL14.2 Soft TTL 与 Hard TTL14.3 TTL Jitter15. 主动失效:写入成功后应该发生什么15.1 RAG 失效事件15.2 优先推进 Revision,而不是枚举所有 Key16. Cache Stampede、Penetration 与 Avalanche16.1 Stampede / Breakdown16.2 Cache Penetration16.3 Cache Avalanche17. Single-flight 正确实现17.1 锁需要唯一 Token17.2 Lock TTL 必须覆盖 Loader 上限17.3 等待者策略17.4 成功、失败和 Partial 分 Namespace18. Stale-While-Revalidate 与 Stale-if-Error18.1 Stale-While-Revalidate18.2 Stale-if-Error18.3 不允许 Stale 的场景19. 多租户与权限隔离19.1 最低要求19.2 User ID 不是完整 Scope19.3 共享结果的安全策略19.4 Revocation19.5 加密与 Redis 隔离20. Cache Key 的 Go 实现21. Cache-aside 读取流程21.1 Cache Failure 不应拖垮主链路22. 数据模型与 Cache Manifest23. 可观测性23.1 RAG Trace 中的位置23.2 指标23.3 Hit Rate 不是唯一目标24. 成本与容量模型24.1 每层价值24.2 Value Size24.3 Memory Budget24.4 Eviction Policy25. 预热与发布25.1 什么时候预热25.2 预热不能绕过权限25.3 版本发布顺序25.4 回滚26. 测试策略26.1 Key Golden Tests26.2 Security Tests26.3 Invalidation Tests26.4 Stampede Tests26.5 Chaos Tests27. Release Gate28. 常见反模式29. P0 / P1 / P2 落地路线P0:安全的精确缓存P1:Context / Answer 与发布治理P2:高级缓存30. 上线检查清单总结官方资料与相关阅读