phase 5 part 3: mongodb, elasticsearch hybrid search, data-pipeline, nodejs BFF topics with verified examples

This commit is contained in:
Shikui Wang committed 2026-10-23 16:38:52 +08:00
1 parent 9eeb10aede
commit 075872e31e
27 files changed
+2405 -52

No files matched your search

@@ -0,0 +1,85 @@
---
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,比基线差不上线。”