482 words
2 minutes
my own enterprise RAG double cache
原有query查询分析
Query: "Token管理" │ ├─ Query Expansion → 5 sub-queries (Token管理, Token, JWT, ...) │ ├─ For EACH sub-query: │ ├─ DashScope embed() ← 网络 I/O, 慢 │ ├─ PostgreSQL BM25 FTS ← DB I/O │ └─ PostgreSQL vector sim ← DB I/O │ ├─ RRF Fusion ← 快 │ └─ DashScope rerank() ← 网络 I/O, 慢缓存策略分层
请求 │ ▼ ┌─────────────────────┐ │ L1: Query Result Cache │ ← 相同 query 直接返回 (TTL 5min) │ (In-memory LRU) │ └─────────────────────┘ │ miss ▼ ┌─────────────────────┐ │ L2: Embedding Cache │ ← 相同 text → embedding (持久化) │ (SQLite / Redis) │ └─────────────────────┘ │ miss ▼ ┌─────────────────────┐ │ L3: BM25 Index Cache │ ← 启动时加载, 增量更新 │ (In-memory) │ └─────────────────────┘ │ ▼ DashScope API + PostgreSQL (pgvector + FTS)各层详解
L1: Query Result Cache(命中率最高)
- 缓存键:skill_point + top_k → RetrievalResult
- TTL:5 分钟(可配置)
- 淘汰:LRU,1000 条上限
- 命中场景:用户反复查询同一个技能点
L2: Embedding Cache(减少 DashScope 调用)
- 缓存键:hash(text) → embedding_vector
- 持久化:SQLite 文件(embeddings.db),启动时加载
- 命中场景:同一文档块被不同 query 引用;query expansion 生成的同义词复用
- 注意:需要评估 DashScope 的 https://help.aliyun.com/zh/dashscope/developer-reference/api-details-for-text-embedding-v3
L3: BM25 Index(消除 DB BM25 查询延迟)
- 启动时从 PostgreSQL 加载全量文本,构建内存 BM25 索引
- 增量更新:文档变更时只更新对应部分,不全量重建
- 或更简单:每次 build_index.py 后将 BM25 数据序列化到文件,API 启动时反序列化
最简可行方案:L1 + L3
L2(embedding 缓存)实际上可以简化——因为 build_index.py 已经把 embedding 存进 pgvector 了,search_similar_chunks 直接查 pgvector 就行。
只加 L1(Query Result Cache)和 L3(BM25 Index):
启动时: ├─ 从 pgvector 加载所有 chunk embeddings → HybridRetriever └─ 从 PostgreSQL 加载所有 chunk 文本 → BM25 index
请求时: ├─ Check L1 cache → hit? return ├─ Query Expansion ├─ BM25 (in-memory, 快) ├─ Vector search (pgvector, 已有索引) ├─ RRF + Rerank └─ Store result in L1 cache性能提升:
- BM25 FTS 查询:从 pgvector round-trip 变成 内存操作,快 10-100x
- Query 结果缓存:重复查询 零数据库调用
my own enterprise RAG double cache
https://sgjki547.top/posts/rag-multi-cache/