RAG 系统分层缓存架构:从单一向量检索到多级缓存的设计演进 📅 2026/7/13 13:18:00 RAG 系统分层缓存架构从单一向量检索到多级缓存的设计演进一、RAG 系统的两个性能瓶颈一个知识库 RAG 系统每天处理 5000 次检索请求每次请求需要经过向量数据库检索 → 结果重排序 → 大模型生成三个环节。监控数据揭示了两个瓶颈一是热门问题的重复检索——如何重置密码这个问题每天被检索 120 次但每次都需要完整走一遍检索→重排→生成流程浪费了 119 次重复计算二是向量检索本身的延迟——在 100 万条文档的向量库中做 ANN 检索平均耗时 180ms加上重排序和模型生成后总耗时超过 2 秒。这两个瓶颈的解决思路都是缓存但缓存的层级和粒度不同。对完全相同的查询词做结果缓存Cache-Aside 模式对语义相似但措辞不同的查询做语义缓存对高频文档片段做预热加载。二、四层缓存架构按数据新鲜度要求和访问频率建立四层缓存graph TB Q[用户查询] -- L1{第一层精确缓存} L1 --|命中| R1[直接返回结果] L1 --|未命中| L2{第二层语义缓存} L2 --|命中| R2[返回相似查询的结果] L2 --|未命中| L3{第三层向量检索} L3 -- VDB[(向量数据库)] VDB -- L4{第四层片段缓存} L4 --|命中| R4[返回缓存的 Top-K 片段] L4 --|未命中| GEN[完整检索 重排] GEN -- LLM[大模型生成] LLM -- RESP[返回 写入缓存] style L1 fill:#c8e6c9 style L2 fill:#fff9c4 style L4 fill:#e1f5fe第一层精确缓存以查询词的 MD5 为键适合一字不差的重复查询。第二层语义缓存以查询的向量表示为键通过相似度匹配找到历史相似查询的结果。第三层是标准的向量检索。第四层片段缓存将高频文档的向量片段常驻内存跳过磁盘 I/O。三、语义缓存的实现 语义缓存层——基于向量相似度的查询缓存。 设计意图对措辞不同但语义相近的查询返回相同结果 比精确缓存覆盖率更高比向量检索延迟更低。 import hashlib import json from typing import Optional, List, Tuple from dataclasses import dataclass from datetime import datetime, timedelta import numpy as np dataclass class CachedQuery: query_text: str query_vector: np.ndarray result: str cached_at: datetime ttl_seconds: int 300 class SemanticCache: def __init__( self, similarity_threshold: float 0.95, max_cache_size: int 10000, ): self.threshold similarity_threshold self.max_size max_cache_size # 使用两个索引向量索引用于语义匹配哈希索引用于精确查找 self.vector_cache: List[CachedQuery] [] self.exact_cache: dict[str, CachedQuery] {} def get_exact(self, query: str) - Optional[str]: 精确缓存——查询词完全一致 key hashlib.md5(query.encode()).hexdigest() entry self.exact_cache.get(key) if not entry: return None # TTL 检查——过期缓存自动淘汰 if datetime.now() - entry.cached_at timedelta(secondsentry.ttl_seconds): del self.exact_cache[key] return None return entry.result def get_semantic( self, query_text: str, query_vector: np.ndarray, ) - Optional[str]: 语义缓存——通过向量相似度匹配 if not self.vector_cache: return None # 计算查询向量与缓存中所有向量的余弦相似度 cached_vectors np.stack([c.query_vector for c in self.vector_cache]) similarities np.dot(cached_vectors, query_vector) / ( np.linalg.norm(cached_vectors, axis1) * np.linalg.norm(query_vector) ) # 找到最高相似度 max_idx int(np.argmax(similarities)) max_sim float(similarities[max_idx]) if max_sim self.threshold: entry self.vector_cache[max_idx] # TTL 检查 if datetime.now() - entry.cached_at timedelta(secondsentry.ttl_seconds): return entry.result # 过期缓存移除 del self.vector_cache[max_idx] return None def set( self, query_text: str, query_vector: np.ndarray, result: str, ttl_seconds: int 300, ) - None: 写入双层缓存 entry CachedQuery( query_textquery_text, query_vectorquery_vector, resultresult, cached_atdatetime.now(), ttl_secondsttl_seconds, ) # 精确缓存——以 MD5 为键 key hashlib.md5(query_text.encode()).hexdigest() self.exact_cache[key] entry # 语义缓存——追加到向量列表 self.vector_cache.append(entry) # LRU 淘汰——超过最大容量时移除最早的条目 if len(self.vector_cache) self.max_size: removed self.vector_cache.pop(0) key hashlib.md5(removed.query_text.encode()).hexdigest() self.exact_cache.pop(key, None) def clear_expired(self) - int: 清理过期缓存——定时任务调用 now datetime.now() before len(self.vector_cache) self.vector_cache [ c for c in self.vector_cache if now - c.cached_at timedelta(secondsc.ttl_seconds) ] return before - len(self.vector_cache)三个设计考量双层索引——精确缓存的 MD5 查找是 O(1) 的语义缓存的向量匹配是 O(n) 的但覆盖率更高LRU 淘汰而非 TTL-only——在缓存满时优先移除最旧条目而非随机淘汰TTL 和 LRU 的组合——过期数据即使未被淘汰也不会返回双重保障缓存新鲜度。四、缓存层的适用边界语义缓存不适合实时性要求高的场景。如果知识库每 5 分钟更新一次TTL300 的缓存意味着用户可能看到 5 分钟前的旧数据。对于新闻、股价等场景应关闭语义缓存或缩短 TTL。缓存一致性的代价。当知识库中的一篇文档被删除后缓存中仍然保留着基于该文档的检索结果。删除操作需要同步失效缓存但哪些缓存条目引用了被删除的文档这个反向索引的维护成本很高。折中方案是文档变更时全部清空缓存代价是接下来一段时间的缓存命中率为零。语义缓存的相似度阈值调优。阈值设为 0.95 时只有高度相似的查询才会复用结果覆盖率低但准确率高。阈值设为 0.85 时覆盖率上升但准确率下降——如何重置密码和如何修改密码的相似度约 0.88两者复用同一结果可能导致答案不精确。五、总结RAG 系统分层缓存架构的设计原则四层缓存按命中速度排列精确缓存O(1)→ 语义缓存O(n)→ 向量检索~200ms→ 片段缓存减少磁盘 I/O双层索引让精确查询和语义查询各有优化的查找路径TTL LRU 双重淘汰策略保障缓存质量和空间效率。落地建议先上线精确缓存——实现最简单收益最直接70% 的重复查询仅占 20% 的查询量收集一周的查询日志分析语义相似的查询比例决定是否投入语义缓存知识库变更时通过 Webhook 触发缓存批量失效监控缓存命中率、语义缓存误匹配率、缓存平均 TTL 三项指标。