← note 架构与系统 · 2026-04-20

my own enterprise RAG double cache

为查询分析检索管线设计 L1/L2/L3 三层缓存,并给出以 L1 查询结果缓存加内存 BM25 索引为核心的最简可行方案。

2 min read

原有query查询分析

graph TD
    Q["Query: Token管理"] --> EXP["Query Expansion → 5 sub-queries (Token管理, Token, JWT, ...)"]
    EXP --> SUB["For EACH sub-query"]
    SUB --> EMB["DashScope embed() ← 网络 I/O, 慢"]
    SUB --> BM["PostgreSQL BM25 FTS ← DB I/O"]
    SUB --> VS["PostgreSQL vector sim ← DB I/O"]
    EMB --> RRF["RRF Fusion ← 快"]
    BM --> RRF
    VS --> RRF
    RRF --> RER["DashScope rerank() ← 网络 I/O, 慢"]

缓存策略分层

graph TD
    REQ["请求"] --> L1["L1: Query Result Cache (In-memory LRU) ← 相同 query 直接返回 (TTL 5min)"]
    L1 -->|miss| L2["L2: Embedding Cache (SQLite / Redis) ← 相同 text → embedding (持久化)"]
    L2 -->|miss| L3["L3: BM25 Index Cache (In-memory) ← 启动时加载, 增量更新"]
    L3 --> DS["DashScope API + PostgreSQL (pgvector + FTS)"]

各层详解

L1: Query Result Cache(命中率最高)

  • 缓存键:skill_point + top_k → RetrievalResult
  • TTL:5 分钟(可配置)
  • 淘汰:LRU,1000 条上限
  • 命中场景:用户反复查询同一个技能点

L2: Embedding Cache(减少 DashScope 调用)

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 就行。

graph TD
    subgraph START["启动时"]
        A["从 pgvector 加载所有 chunk embeddings → HybridRetriever"]
        B["从 PostgreSQL 加载所有 chunk 文本 → BM25 index"]
    end

    subgraph REQ["请求时"]
        C["Check L1 cache → hit? return"]
        D["Query Expansion"]
        E["BM25 (in-memory, 快)"]
        F["Vector search (pgvector, 已有索引)"]
        G["RRF + Rerank"]
        H["Store result in L1 cache"]
        C --> D --> E
        E --> F --> G --> H
    end

性能提升:

  • BM25 FTS 查询:从 pgvector round-trip 变成 内存操作,快 10-100x
  • Query 结果缓存:重复查询 零数据库调用