--- id: backend.elasticsearch.scenario.kb-search-sync title: 知识库混合检索:数据库到 ES 的同步、权限过滤、分词词典和检索效果评估 type: scenario domain: backend stack: [elasticsearch, postgresql, mq, rag] tags: [混合检索, 数据同步, CDC, outbox, 权限过滤, IK 词典, 召回率] level: intermediate prerequisites: [backend.elasticsearch.search-hybrid, ai.rag.hybrid-rerank] est_hours: 2.5 related: [backend.elasticsearch.interview, ai.rag.scenario.permission-aware-kb, backend.mq.reliability] week: [6, 14] --- # 知识库混合检索:数据库到 ES 的同步、权限过滤、分词词典和检索效果评估 ## 业务背景与问题 企业知识库 RAG 一开始只用了 pgvector 向量检索,问题: 1. 用户搜产品型号、错误码、合同编号,向量检索经常找不到; 2. 文档在后台修改后,问答仍引用旧内容; 3. 偶尔检索到用户无权查看的部门文档; 4. 每次调整分词或检索参数,不知道效果是变好还是变差。 ## 约束 - 文档的主数据在 PostgreSQL(含权限)。 - 修改后 1 分钟内可检索到。 - 不能引入复杂的大数据组件。 ## 方案设计 1. **检索层**:ES 索引同时保存分块的文本(IK 分词 + 业务词典:产品型号、内部术语)和向量;查询时 BM25 和 kNN 两路检索,应用层 RRF 融合,再用重排模型取前 5。 2. **权限**:每个分块冗余 `tenant_id`、`dept_ids`、`visibility` 等 `keyword` 字段;查询时把当前用户的权限转换成 `filter`,两路检索都带上;返回前再按数据库做一次最终校验(防止同步延迟导致的权限变更未生效)。 3. **同步**: - 文档修改时在同一个数据库事务中写入 outbox 表; - 后台任务读取 outbox 发到消息队列; - 消费者重新分块、计算向量、按“文档 id + 分块序号”写入 ES(幂等),删除多余的旧分块; - 失败重试,超过次数进入死信并告警; - 每天对账:数据库与 ES 的文档版本号比对,修复差异。 4. **变更映射**:通过别名 + 新索引重建,不停机。 5. **评估**:维护一份标注集(问题 → 应该命中的文档),每次调整分词词典、权重、`num_candidates` 都计算召回率@K 和 MRR,比基线差就不上线。 ## 关键代码 权限条件同时作用于两路检索: ```python def acl_filter(user) -> list[dict]: return [ {"term": {"tenant_id": user.tenant_id}}, {"bool": {"should": [ {"term": {"visibility": "public"}}, {"terms": {"dept_ids": user.dept_ids}}, ], "minimum_should_match": 1}}, ] kw = es.search(index="kb", size=20, query={"bool": {"must": {"match": {"text": q}}, "filter": acl_filter(user)}}) vec = es.search(index="kb", size=20, knn={"field": "vec", "query_vector": embed(q), "k": 20, "num_candidates": 200, "filter": acl_filter(user)}) candidates = rrf(ids(kw), ids(vec))[:20] top5 = rerank(q, candidates)[:5] ``` 完整可运行的基础示例:[examples/hybrid-search](../examples/hybrid-search/);权限设计详见[权限感知知识库](../../../ai/rag/scenarios/permission-aware-kb.md)。 ## 取舍 | 选择 | 优点 | 缺点 | | --- | --- | --- | | **ES 做混合检索(本方案)** | 关键词能力强、过滤和聚合成熟、向量也支持 | 多一套集群和同步链路 | | 只用 pgvector + PG 全文检索 | 组件少、强一致 | 中文分词和相关性调优能力弱一些 | | 专用向量库 + ES | 各自最强 | 两套检索系统,复杂度最高 | ## 易错点 - 只同步新增和修改,忘记处理删除和权限变更。 - 消费者不是幂等的,重试后出现重复分块。 - 业务词典更新后没有重建索引(已写入的数据仍按旧分词)。 - 只凭几个例子判断效果,没有标注集和指标。 ## 面试怎么讲 > “纯向量检索对型号、错误码这种精确词召回很差,我们引入 ES 做混合检索:IK 分词加业务词典,BM25 和 kNN 两路召回,应用层 RRF 融合后再重排。权限字段冗余到每个分块,作为 filter 同时作用于两路检索,返回前再校验一次。同步用 outbox + 消息队列,消费者按文档 id+分块号幂等写入,每天对账。每次调参都用标注集跑召回率@K 和 MRR,比基线差不上线。”