RAG知识库查询缓存与热点问题命中优化落地

日期:2026-11-08

一、项目背景

我们给一家企业做的知识库 RAG 上线后,使用量起来得比预期快。但很快发现,每天真正被问的高频问题就那么几百个,大量的重复 query 每次都走完整的"向量检索加生成"流水线,向量库被反复打同样的请求,推理侧也在重复生成几乎一样的答案。成本曲线和调用量同步飙升,高峰期向量库延迟把整体响应拖慢,用户体验反而下降。

我们当时上来就想着堆算力、扩向量库节点,预算批下来之前先自己算了一笔账:同一个"年假怎么算"这类问题,一天被问几千次,每次都重算,这才是成本大头。这个项目就是要把重复检索和重复生成从根上砍掉,而不是无脑加机器。

二、落地场景

落地后的逻辑是,接入层在请求进 RAG 流水线之前,先过一层语义缓存。高频 query 经过归一化处理后查缓存,命中就直接返回已生成的答案和引用来源,跳过向量检索和推理。只有冷 query 才走完整流水线,生成结果后回填缓存。

语义缓存的键不是原始字符串,而是经过归一化(去停用词、同义归并、小写化)后的语义指纹,这样"年假如何计算"和"年假怎么算"能命中同一条。缓存带时效,知识库更新后相关条目自动失效,避免返回过期答案。后台看缓存命中率、平均响应时长、向量库调用下降比例和推理成本下降这几条线。

我们当时还加了一道缓存击穿防护,防止某个冷 key 突然变热、瞬间几千请求同时穿透到向量库。做法是单 key 首次回填时加短暂互斥锁,后续请求等结果而非各自去查,把穿透压成一次。

三、关键技术挑战与解决思路

绕不开的是语义缓存键归一。字面相同的 query 好办,难的是语义相同字面不同。我们做了轻量归一:统一全半角、去标点、同义词表归并、按业务域分桶,归并后取稳定指纹。归并太激进会把不同问题并成一条,太保守又命中率低,阈值靠线上数据慢慢调。

紧接着要处理的是缓存时效与新鲜度。知识库天天在变,缓存不能永久。我们按知识库更新事件做主动失效,相关文档变动就标记关联缓存条目过期,而不是统一短过期。这样既保了命中率,又不至于返回过时答案。

再往下是命中与新鲜度的权衡。有些答案时效性强(比如当天政策),不该长缓存。我们给 query 打了时效标签,强时效类不进长缓存,只做极短的结果复用,平衡安全和成本。

最后是缓存击穿。热点 key 失效瞬间最危险,我们用单 key 回填锁,首个未命中请求负责生成并回填,其余等待,避免几千请求同时打穿。这个锁超时很短,防止回填慢把后续请求饿死。

案例片段(已脱敏): 语义缓存归一与击穿防护配置(节选):semantic_cache:  normalize: [fullwidth, stopword, synonym_merge]  domain_bucket: true  ttl_policy: event_invalidate      # 按知识库更新事件失效  freshness_tag: [policy_today]      # 强时效类不进长缓存  penetration_guard:    refill_lock_ms: 1500    wait_on_miss: true运行日志:缓存命中率 61%,平均响应由 2.8s 降至 0.9s,向量库调用降 58%。

四、效果数据

运行一个月,语义缓存命中率稳定在六成一左右,也就是说超过六成的请求完全跳过了向量检索和推理。平均响应时长从二点八秒降到零点九秒,用户体感最明显的是高峰期不再卡。向量库调用量下降约五成八,推理成本同步下降,折算下来月度算力账单少了一大块。

缓存击穿防护在几次知识库大批量更新后的峰值中验证过,单 key 回填锁把穿透请求数从数千压到一次,向量库没有再被瞬间打挂。强时效类 query 因不进长缓存,过期答案零投诉。文中数据为项目复盘口径,均为示意值,非审计级精确数字。

我们当时低估了同义词归并的工作量,最初版本命中率只有四成,补了业务域同义词表后才到六成。这块没有捷径,得结合真实 query 日志一点点补。

五、可复用经验总结

RAG 上线后成本大头往往是重复检索和重复生成,先别急着堆机器,把高频问题缓存住,性价比高得多。语义缓存的键要归一化,字面不同语义相同的问题要能并成一条,但归并阈值别太激进,不然把不同问题并错更糟。

缓存失效别用统一短过期,按知识库更新事件主动失效才准,既保命中又保新鲜。热点 key 失效瞬间的击穿最危险,单 key 回填锁是必须的,否则一次失效就能把向量库打挂。同义词表没有捷径,得靠真实 query 日志慢慢补。

结语

这个项目让我明白,RAG 上线后最该省的钱在重复检索,不是堆机器,但我们当时上来就想着扩向量库节点。同义词表那块没有捷径,得靠真实 query 日志一点点补。等命中率上来,月度算力账单的下降最直观,比任何架构图都管用。强时效类要单独划出来,不然迟早返回过期答案被投诉。

顺带,回填锁的超时别设太长,我们一开始设大了,结果回填慢时后续请求干等,反而把体验拖差。命中率也不是越高越好,强时效类被长缓存反而会返回过期答案,这条线得按业务划清楚。