☰
缓存会造成什么错误?
2026/10/14 18:23:19 网站建设 项目流程

第208题:缓存会造成什么错误?

1. 核心结论

在 Agent 或 LLM 系统中,缓存能够降低 latency 和 cost,但会额外引入一层状态。

缓存相关问题主要可以归纳为五类:

  1. Stale Cache:返回过期结果
  2. Wrong-key / Cross-tenant Reuse:错误请求命中同一个缓存
  3. Semantic False Hit:语义相似,但实际答案不同
  4. Cache Poisoning:错误或恶意内容被缓存并持续传播
  5. Cache Stampede:热点缓存失效后大量请求同时回源

因此设计缓存时,我会同时控制:

Cache Key + TTL + Version + Tenant/Permission + Invalidation + Concurrency

并针对不同缓存层分别设计策略。


2. 首先区分 Agent 系统中的几种缓存

2.1 Prompt / Prefix Cache

这一层复用模型已经计算过的公共 Prompt 前缀,例如:

System Prompt + Tool Definitions + Static Context

如果后续请求拥有相同前缀,就可以复用前缀计算。

这一类缓存主要影响:

  • Token Cost;
  • Prefill Latency;
  • Cache Hit Rate。

以 OpenAI Prompt Caching 为例,缓存命中依赖 Prompt 前缀匹配。

因此:

Prompt 顺序变化

Tool Schema 变化

System Prompt 变化

都可能导致 Cache Miss。

这一层通常不会直接把上一次完整答案返回给下一次请求,所以它和 Response Cache 的错误模式需要分开分析。


2.2 Exact Response Cache

这一层直接缓存:

Request → Final Response

例如:

Key: "如何重置密码?" Value: "进入账户设置..."

下一次相同请求直接返回已有答案,可以完全跳过 LLM。

这种方式节省的成本最大,但对 Cache Key 的正确性要求也最高。


2.3 Semantic Cache

Semantic Cache 进一步允许:

"怎么重置密码?"

命中:

"如何修改我的登录密码?"

通常先计算 Query Embedding:

q=fembed(x) q=f_{\text{embed}}(x)q=fembed​(x)

然后与已有缓存 Query 比较:

sim(q,qi) sim(q,q_i)sim(q,qi​)

如果:

sim(q,qi)>τ sim(q,q_i)>\tausim(q,qi​)>τ

就直接返回对应缓存答案。

它能够提高命中率,但会产生一个新的问题:

语义相似不能自动保证任务等价。


2.4 RAG / Tool / Data Cache

Agent 还经常缓存:

  • Retrieval Result;
  • Embedding;
  • Search Result;
  • Database Query;
  • Tool Result;
  • API Response;
  • User Profile;
  • Permission Result。

这一类缓存主要面临:

源数据已经变化,但缓存没有同步变化

导致的状态一致性问题。


3. 第一类错误:Stale Cache

最典型的问题是缓存陈旧。

例如上午缓存:

漏洞状态 = 未修复

下午漏洞已经修复:

漏洞状态 = 已修复

Agent 仍然命中旧缓存:

漏洞状态 = 未修复

这就是 Stale Cache。

RFC 9111 对 HTTP Cache 也明确区分 Fresh Response 和 Stale Response,并规定了缓存过期和重新验证机制。

Agent 场景中更危险的陈旧状态包括:

  • 用户权限已经撤销;
  • 防火墙规则已经修改;
  • 知识库已经更新;
  • Ticket 状态已经变化;
  • 软件版本已经升级;
  • 威胁情报已经更新;
  • System Prompt 已经换版本;
  • 模型已经升级。

因此 TTL 不能统一设置。

例如:

静态产品说明 TTL = 24h

可能合理。

但:

权限状态 安全告警 实时资产状态

可能需要很短 TTL,甚至直接绕过缓存读取 Source of Truth。


4. 第二类错误:Cache Key 不完整

这通常是 Agent 系统里更危险的问题。

假设系统只使用:

hash(user_query)

作为 Cache Key。

用户 A 问:

我的服务器有哪些高危漏洞?

系统缓存:

CVE-X CVE-Y

用户 B 随后提出相同问题。

如果 Cache Key 没有 Tenant 信息:

用户B ↓ 相同Query ↓ Cache Hit ↓ 返回用户A的数据

就形成跨租户数据泄漏。

因此一个生产级 Response Cache Key 至少需要考虑:

K=H(request,tenant,permission,model,prompt,tools,dataVersion) K=H( request, tenant, permission, model, prompt, tools, dataVersion )K=H(request,tenant,permission,model,prompt,tools,dataVersion)

实际可以包含:

tenant_id user_scope authorization_version normalized_query model_id model_version system_prompt_version tool_schema_version retrieval_index_version locale

其中任何能够改变最终答案的变量,都应该进入:

Cache Key

或者进入:

Cache Eligibility Decision


5. 权限不能只在写入缓存时检查

这是一个容易出现的漏洞。

假设管理员查询:

公司所有高危漏洞

并把结果缓存。

普通员工随后查询相似问题。

如果系统直接读取管理员生成的 Cache Entry:

Admin Permission ↓ Cached Response ↓ Normal User

权限边界已经被缓存绕过。

因此需要:

Authorization ↓ Cache Lookup

而且 Cache Entry 本身也应该绑定:

Tenant Role Scope ACL Version

当权限发生变化时,相应缓存需要:

Invalidate

或者通过新的权限版本自然形成 Cache Miss。


6. 第三类错误:Semantic Cache False Hit

Semantic Cache 的主要风险是:

Embedding 很接近,业务约束不同。

例如:

“删除测试环境数据库”

和:

“删除生产环境数据库”

语义上高度相似。

但两个操作的安全级别完全不同。

再例如:

“张三有哪些权限?”

和:

“李四有哪些权限?”

句子结构高度相似,答案完全依赖具体实体。

因此 Semantic Cache 不能只依赖:

cosine similarity cosine\ similaritycosinesimilarity

更合理的判断是:

Semantic Similarity + Hard Metadata Filter

例如先限制:

tenant = A locale = zh-CN model_version = V3 permission_scope = security_read knowledge_version = 2026-08-31

然后再计算语义距离。

Redis 官方 Semantic Cache 文档也明确提出应结合:

  • Tenant;
  • Locale;
  • Model Version;
  • Safety Flags;

等 metadata boundary。

另外还需要调节相似度阈值τ\tauτ:

τ↑ \tau \uparrowτ↑

通常意味着:

False Hit ↓ Hit Rate ↓

而:

τ↓ \tau \downarrowτ↓

通常意味着:

Hit Rate ↑ False Hit ↑

最终阈值应该通过真实数据验证。


7. 第四类错误:Cache Poisoning

如果错误内容或恶意内容进入共享缓存,它会持续影响后续请求。

例如攻击者构造一个请求,使系统产生:

错误安全建议

随后这条结果被写入:

Shared Cache

之后其他用户只要命中相同 Key,就会继续收到错误内容。

形成:

一次错误 ↓ Cache Write ↓ 大量 Cache Hit ↓ 错误被持续放大

这就是 Cache Poisoning 的核心风险。

OWASP 对 Web Cache Poisoning 的描述也指出,共享缓存能够把一次恶意响应的影响扩散给后续用户。

在 LLM Agent 中,缓存写入之前最好检查:

  • 输出是否通过安全策略;
  • 数据来源是否可信;
  • Tool Execution 是否成功;
  • Response 是否完整;
  • 是否来自 fallback;
  • 是否发生 timeout;
  • 是否包含敏感信息。

特别需要避免缓存:

Error Response Partial Response Timeout Result Unauthorized Response Temporary Failure

否则临时故障可能被固化成持续故障。


8. 第五类错误:Cache Stampede

假设一个热门 Key:

K_hot

每秒有:

10000 requests

正常情况下:

10000 requests ↓ Cache ↓ 1 个缓存结果

数据库或 LLM 几乎没有压力。

如果这个 Key 在某个瞬间过期:

K_hot expires

大量请求会同时发现:

Cache Miss

然后一起:

调用 LLM 查询数据库 调用外部 API

可能形成:

Cache Expire ↓ 10000 Cache Miss ↓ 10000 Origin Requests ↓ Origin Overload

这就是 Cache Stampede,也常被称为缓存击穿或 Thundering Herd。

Redis 官方 Cache-Aside 文档明确描述了热门 Key 同时过期导致多个进程一起回源的问题。


9. Cache Stampede 怎么解决

常见做法是 Single-flight 或 Per-key Lock。

第一个请求发现:

Cache Miss

获得:

Lock(K)

然后只有这个请求真正回源:

LLM / DB / API

其他请求:

Wait

等第一个请求完成缓存更新后:

Cache Hit

形成:

Request 1 ↓ Acquire Lock ↓ Origin ↓ Write Cache Request 2...N ↓ Wait ↓ Read Cache

这个锁还必须具有:

Timeout Owner Token Lease

否则负责更新缓存的 Worker 崩溃后可能造成永久阻塞。

对于大量 Key 同时过期,还可以给 TTL 增加随机抖动:

TTL=T+ϵ TTL=T+\epsilonTTL=T+ϵ

其中:

ϵ∼Random(−δ,+δ) \epsilon\sim Random(-\delta,+\delta)ϵ∼Random(−δ,+δ)

避免所有缓存同一时间失效。


10. 第六类问题:缓存和 Source of Truth 不一致

Cache-Aside 常见流程是:

Read: Cache ↓ miss Database ↓ Write Cache

而更新时:

Write Database ↓ Invalidate Cache

其中存在一致性窗口。

例如:

DB = V2 Cache = V1

某些请求仍然可能读到:

V1

Microsoft Azure Cache-Aside 文档明确指出,这一模式无法天然保证 Cache 与 Data Store 的强一致性。

因此需要先定义业务能容忍多少陈旧:

Staleness Budget

例如:

新闻推荐:几分钟可能可以接受
银行余额:通常要求更严格
权限撤销:安全场景通常不能依赖长TTL

对于强一致需求,可以:

  • 直接 bypass cache;
  • 写入时同步 invalidation;
  • 使用 versioned key;
  • 使用 write-through;
  • 使用事件驱动失效。

11. Versioned Cache 比单纯 TTL 更可靠

例如当前:

model_version = v4 prompt_version = p17 knowledge_version = k23

Cache Key 可以构造成:

agent:v4:p17:k23:<query_hash>

当 Prompt 升级到:

p18

新的请求自动进入:

agent:v4:p18:k23:<query_hash>

旧缓存不会继续命中。

这种方法特别适合:

  • 模型升级;
  • System Prompt 升级;
  • Tool Schema 改动;
  • RAG Index 重建;
  • Embedding Model 更换。

可以降低复杂的批量删除需求。


12. 缓存故障还会影响模型评测

假设比较:

Model A

和:

Model B

但 Model B 的测试请求大量命中了旧的:

Model A Response Cache

那么最终测到的结果就失去意义。

因此实验和线上灰度需要把:

model_version prompt_version experiment_id

加入 Cache Namespace。

A/B Test 也需要隔离:

experiment_A experiment_B

避免两个实验组互相污染。


13. Agent 系统中我会怎样设计缓存

可以采用分层缓存:

Request ↓ Authorization ↓ Cache Eligibility Check ↓ Exact Cache ↓ miss Semantic Cache ↓ miss Agent / RAG / Tool Execution ↓ Output Validation ↓ Cache Write

其中 Cache Key 至少绑定:

Tenant User Scope Model Version Prompt Version Tool Version Knowledge Version Request

缓存条目记录:

created_at expires_at source_version model_version prompt_version tenant permission_scope quality_status

这样发生错误时能够追踪:

这条答案为什么被缓存、来自哪个模型、哪个 Prompt 和哪个数据版本。


14. 哪些内容我通常不会缓存

以下数据需要非常谨慎:

  • 实时权限状态;
  • 高风险审批结果;
  • 一次性 Token;
  • 密码和 Secret;
  • 动态安全策略;
  • 高时效威胁情报;
  • 不确定是否包含敏感信息的模型输出;
  • 执行失败结果;
  • 部分完成的 Agent Task。

缓存带来的收益需要与:

错误成本

一起评估。

如果一次错误复用可能造成严重安全影响,绕过缓存通常更加合理。


15. 怎么验证缓存设计

我会主动构造下面几组测试。

15.1 Stale Test

先缓存:

Document Version = V1

然后把源数据更新为:

V2

验证系统是否按照定义的时间或事件:

Invalidation / Refresh

15.2 Cross-tenant Test

让:

Tenant A Tenant B

发送完全相同 Query。

验证:

A Response

无法被:

B

命中。


15.3 Permission Change Test

用户原来有:

admin

随后降权为:

viewer

验证旧缓存无法绕过新的 ACL。


15.4 Semantic False-hit Test

构造表面高度相似但答案不同的问题,例如:

删除测试环境资源 删除生产环境资源

检查 Semantic Cache 是否错误复用。


15.5 Stampede Test

人为让一个热门 Key 同时过期:

N = 1000 concurrent requests

观察:

Origin Request Count

理想状态下不会接近:

1000

而是通过 Single-flight 或 Request Coalescing 显著降低回源数量。


15.6 Poisoning Test

主动写入:

  • 错误答案;
  • 恶意答案;
  • 部分失败答案;
  • Unauthorized Response。

验证这些结果是否能够进入共享缓存并影响正常用户。


16. 应该监控什么指标

缓存层至少需要监控:

Cache Hit Rate Cache Miss Rate Semantic False-hit Rate Stale Response Rate Cross-tenant Violation Origin Request Rate Cache Fill Latency Eviction Rate Invalidation Delay Stampede Amplification

同时继续监控整个 Agent:

Task Success Latency Token Cost Safety Violation

因为极高的 Hit Rate 本身没有价值。

例如:

Hit Rate = 95%

但:

Semantic False-hit = 10%

这个缓存方案显然不可接受。


17. 面试时可以压缩成下面这段

Agent 中缓存主要会产生五类错误。

第一类是陈旧数据。知识库、权限、模型或工具状态已经变化,但缓存仍然返回旧结果,所以要用 TTL、事件失效和版本化 Key 控制 freshness。

第二类是 Cache Key 不完整。如果 Key 没有包含 tenant、权限、模型版本、Prompt 版本和数据版本,就可能出现跨用户、跨模型或跨实验复用,安全系统里尤其可能造成数据泄漏。

第三类是 Semantic Cache False Hit。两个问题 embedding 很相似,但实体、时间、权限或环境不同,答案可能完全不同,所以语义相似度还需要结合 tenant、locale、model version、permission 等硬 metadata filter。

第四类是 Cache Poisoning。错误或恶意结果一旦进入共享缓存,会被后续请求持续放大,所以缓存写入前也要做输出和权限验证。

第五类是 Cache Stampede。热点 Key 同时过期后,大量请求会一起回源,可能把数据库、工具服务或 LLM 网关压垮。我会使用 single-flight、per-key lock、TTL jitter、回源限流和熔断控制。

最终我会用 stale、跨租户、权限变更、semantic false hit、热点同时过期和 poisoning 六类故障注入测试验证缓存设计。

核心原则是:

缓存命中必须同时满足内容等价、权限等价和版本等价。


18. 来源

  1. RFC 9111 — HTTP Caching:定义 Fresh/Stale Response、Cache Key、Vary 和 Revalidation 等缓存基础语义。
  2. Microsoft Azure Architecture Center — Cache-Aside Pattern:说明缓存与 Source of Truth 的一致性问题、TTL、失效策略以及 Semantic Cache 的适用边界。
  3. Microsoft Azure Architecture Center — Caching Guidance:说明缓存写入后的数据一致性、陈旧数据和分布式缓存设计问题。
  4. Redis Documentation — Cache-Aside:说明热门缓存 Key 同时过期后产生 Cache Stampede 的机制。
  5. Redis Documentation — Semantic Cache:说明 Semantic Cache 的相似度阈值,以及 tenant、locale、model version、safety flags 等 metadata boundary。
  6. OpenAI — Prompt Caching:说明 Prompt Cache 通过复用重复 Prompt 前缀计算降低 latency 和 input cost,并依赖前缀匹配。
  7. OWASP — Cache Poisoning:说明恶意或错误内容进入共享缓存后可以被持续传播给后续用户。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询