工业级 RAG 为什么需要 Elasticsearch?一篇讲清索引、度量、过滤与 Milvus 选型

0 阅读30分钟

过去提到 Elasticsearch,我首先想到的是搜索引擎、倒排索引、BM25,以及大家比较熟悉的 ELK 日志分析体系。

而提到 Milvus,我首先想到的则是向量数据库、Embedding、HNSW 和相似度检索。

因此,我们很容易形成一个简单的认识:

Elasticsearch 负责关键词检索,Milvus 负责向量检索。

但随着我进一步了解两者的能力,我发现这个结论已经不够准确了。

现在的 Elasticsearch 不仅可以做 BM25,还支持稠密向量、稀疏向量、HNSW、元数据过滤和混合检索;而 Milvus 也不再只是单纯存储稠密向量,它同样开始支持稀疏向量、BM25、标量过滤和混合召回。

这就引出了一个更值得讨论的问题:

既然 Elasticsearch 和 Milvus 都能完成关键词检索、向量检索和元数据过滤,那么工业级 RAG 到底应该使用谁?什么时候只使用 Elasticsearch,什么时候只使用 Milvus,什么时候才需要 Elasticsearch 加 Milvus?

在回答这个问题之前,我们首先要把几个经常混在一起的概念彻底拆开:

  • 索引结构
  • 搜索算法;
  • 相似度度量;
  • 元数据过滤;
  • 排序与重排。

只有先理解一次检索到底发生了什么,后面的技术选型才不会停留在产品名称层面。


一、一次完整的检索,到底发生了什么

很多人会把向量检索简单理解为:

把问题转换成向量,再去数据库里找到最相似的几个向量。

这个描述不能说错,但它省略了很多关键步骤。

一次完整的检索,实际上包含下面几个环节:

这里的每一个环节,解决的问题都不一样。

概念主要解决的问题常见实现
索引结构数据提前如何组织倒排索引、HNSW、IVF
搜索算法查询时如何快速找到候选图遍历、聚类探测、全量扫描
度量方式两个向量如何判断远近Cosine、IP、L2
元数据过滤哪些数据有资格参与检索租户、权限、知识库、状态
召回排序候选结果如何计算初始得分BM25、向量相似度
融合与精排多路候选最终如何排序RRF、BGE-Reranker

其中最容易混淆的,就是索引方式和相似度度量。


1.1 索引结构:数据提前怎么组织

索引的核心目的,不是直接判断哪一条数据最相似,而是:

提前组织数据,让查询时不需要检查全部数据。

假设数据库里有一百万条向量。

最简单的方法是,让查询向量分别和这一百万条向量计算一次相似度,然后再从中选出 Top 10。

这种方式当然能够找到准确结果,但是每次查询都需要完成一百万次向量比较。随着数据规模增加,查询延迟和计算成本都会线性上升。

所以,我们需要提前把向量组织起来。

例如 HNSW 会把相似的向量连接成一张多层近邻图;IVF 会把整个向量空间划分成多个聚类区域;倒排索引则会提前建立“词项到文档”的映射关系。

可以把索引理解成一张提前修建好的地图。

没有地图时,我们只能把整个城市的道路全部走一遍;有了地图之后,就可以先定位大致区域,再进入附近街道,最终找到目标位置。

因此,索引结构解决的是:

数据放在哪里,以及查询时应该先去哪里找。


1.2 搜索算法:沿着索引寻找候选

索引建立完成后,还需要配套的搜索方法。

以 HNSW 为例。

在建立索引时,HNSW 会把向量组织成多层图结构。上层节点少、跨度大,类似高速公路;下层节点多、连接密集,类似城市道路。

查询时,系统会:

  1. 从图的高层入口开始;
  2. 快速靠近查询向量所在的区域;
  3. 逐层向下;
  4. 在局部邻居中继续搜索;
  5. 返回一批可能相似的候选向量。

这意味着,HNSW 并不是单纯的距离公式。

更准确地说,它同时包含:

  • 建索引阶段的图结构构建;
  • 查询阶段的近似图搜索。

所以我们在工程实践中经常把 HNSW 称为“索引算法”,但它解决的核心问题仍然是:

怎样用更少的比较次数,找到尽可能接近真实答案的候选集合。


1.3 度量方式:到底怎样才算相似

当索引算法找到一批候选以后,系统还要判断这些候选和查询向量到底有多相似。

这就需要相似度或者距离函数。

最常见的三种方式是:

  • Cosine:余弦相似度
  • IP:Inner Product,内积;
  • L2:欧氏距离。

可以把三者理解为三种不同的“距离定义”。

假设我要在城市中寻找一家最近的医院,那么“最近”可以有不同含义:

  • 直线距离最近;
  • 驾车距离最近;
  • 驾车时间最短。

它们面对的是同一批医院,但由于“距离”的定义不同,最后得到的结果可能不同。

向量检索也是如此。

Cosine:比较方向

余弦相似度更关注两个向量的方向是否一致,而不太关注向量本身的长度。

在文本 Embedding 中,我们通常更关心两段文本的语义方向是否接近,因此 Cosine 是非常常见的选择。

IP:计算内积

内积的计算方式是:

如果两个向量已经完成 L2 归一化,那么它们的模长都是 1,此时内积和余弦相似度的排序结果基本一致。

BGE-M3 的论文中,稠密检索使用归一化后的表示,并通过内积计算查询与文档的相关性。

L2:计算空间距离

L2 计算的是两个向量在空间中的欧氏距离:

L2 越小,代表两个向量越接近。

因此,我们可以用一句话总结索引和度量的区别:

HNSW、IVF 决定去哪里找;Cosine、IP、L2 决定找到的候选中谁更相似。


1.4 元数据过滤:谁有资格参加检索

工业级 RAG 中还有一个经常被忽略的环节,就是元数据过滤。

真实业务中的检索,通常不是:

从全部文档中找出与问题最相似的内容。

而是:

从当前租户、当前知识库、用户有权限访问、仍处于有效状态的文档中,找到与问题最相关的内容。

例如,一条知识库 Chunk 除了正文和向量,还可能包含:

  • tenant_id:属于哪个租户;
  • kb_id:属于哪个知识库;
  • department:属于哪个部门;
  • doc_type:文档类型;
  • status:文档是否有效;
  • acl_roles:哪些角色可以访问;
  • effective_date:生效时间;
  • expiry_date:失效时间。

用户查询时,真正的检索条件可能是:

元数据过滤解决的是:

哪些数据有资格参与后面的相关性竞争。

它既不是索引算法,也不是相似度计算方式。


1.5 排序、融合和重排

完成初步召回后,还需要决定候选的最终顺序。

例如:

  • BM25 返回一批关键词候选;
  • Dense Vector 返回一批语义候选;
  • Sparse Vector 返回一批学习型稀疏候选。

由于三路检索的分数范围不同,不能简单把它们的原始分数直接相加。

这时可以使用 RRF,也就是 Reciprocal Rank Fusion,根据不同召回通道中的排名进行融合。

Elasticsearch 当前的 Retriever API 可以组合关键词检索、稀疏检索和稠密向量检索,并通过 RRF 统一候选排名。

在 RRF 后面,还可以增加 BGE-Reranker,对查询和候选文本进行更精细的交叉计算。

因此,工业级 RAG 中常见的检索链路是:


二、从传统搜索重新理解 Elasticsearch

理解前面的基础概念以后,我们再回到 Elasticsearch。

Elasticsearch 最初的核心优势不是向量检索,而是全文搜索。

不过在讲全文搜索之前,还需要先解决一个术语问题:

Elasticsearch 中的“索引”到底指什么?


2.1 Elasticsearch 中至少有四种“索引”(重点、重点)

在 Elasticsearch 的语境里,“索引”这个词可能代表不同层次的概念。

第一种:Elasticsearch Index

例如:

  • rag_chunks_v1
  • product_search_v1
  • customer_service_logs_v1

这里的 Index 是一类 JSON 文档的逻辑集合,可以粗略类比关系型数据库中的表。

第二种:倒排索引

倒排索引是全文检索的底层数据结构,负责建立词项和文档之间的映射关系。

第三种:向量索引

例如 HNSW,负责组织 dense_vector 字段中的向量。

第四种:字段索引

例如 keyworddateinteger 字段,也会建立适合精确过滤、范围查询和聚合的数据结构。

所以:

创建一个 Elasticsearch Index,不等于选择了 HNSW 算法。

一个 Elasticsearch Index 内部,可以同时包含:

  • 文本字段的倒排索引;
  • Keyword 字段的精确索引;
  • 日期和数值字段的结构化索引;
  • Dense Vector 字段的 HNSW 索引;
  • Sparse Vector 字段的稀疏索引。

这也是 Elasticsearch 能够把全文检索、向量检索和元数据过滤放在同一份文档中的基础。


2.2 倒排索引解决什么问题

假设有三个文档:

  • 文档一:工业设备故障处理;
  • 文档二:工业设备维护手册;
  • 文档三:医院门诊预约流程。

倒排索引会形成类似下面的结构:

词项对应文档
工业文档一、文档二
设备文档一、文档二
故障文档一
维护文档二
医院文档三
门诊文档三

当用户查询“工业设备故障”时,Elasticsearch 不需要逐个扫描所有文档,而是可以直接根据词项找到对应的文档列表。

Elasticsearch 会先通过 Analyzer 对文本进行分词和归一化,再建立由词项字典和 Posting List 组成的倒排索引。

因此,倒排索引解决的是:

如何快速找到包含相关词项的文档。


2.3 BM25 不是倒排索引

倒排索引找到候选以后,还需要判断哪些文档更加相关。

这就是 BM25 的工作。

BM25 会综合考虑:

  • 查询词在当前文档中出现了多少次;
  • 这个词在整个文档集合中是否稀有;
  • 当前文档是否过长;
  • 同一个词重复出现后的收益递减。

例如“故障”这个词在所有文档中都很少出现,那么包含“故障”的文档通常更值得关注。

而“设备”如果在大量文档中都出现,它对区分文档的作用就会下降。

所以:

倒排索引负责找到候选文档,BM25 负责给这些候选计算关键词相关性分数。

这和向量检索中的关系很相似:

文本检索向量检索
倒排索引HNSW、IVF
BM25Cosine、IP、L2
Posting List向量近邻候选
AnalyzerEmbedding 模型

当然,这只是为了帮助理解,两套体系的底层原理并不完全相同。


三、Elasticsearch 如何完成向量检索

现在的 Elasticsearch 已经能够作为向量数据库使用。

当我们把 Embedding 存入 dense_vector 或 sparse_vector 字段后,就可以基于向量相似度进行检索。


3.1 Dense Vector 是什么

用户输入一段文本:

设备出现故障以后应该怎样处理?

经过 BGE-M3 后,可以得到一个固定维度的稠密向量。

所谓稠密向量,是指向量的大部分维度都有数值。

例如:

[ [0.032,-0.126,0.781,\dots] ]

单个数字本身通常没有直观业务含义,但是整个向量共同表示文本的语义特征。

查询时,用户问题也会被转换成相同维度的向量,再与数据库中的文档向量比较。


3.2 FLAT:最直接的搜索方式

FLAT 可以理解为全量比较。

假设数据库中有十万条向量,那么查询时就分别计算查询向量与十万条向量之间的距离。

它的优点是:

  • 实现简单;
  • 不会因为近似索引而漏掉真实近邻;
  • 适合作为召回率基准。

缺点也非常明显:

  • 数据量越大,计算次数越多;
  • 查询延迟随数据规模增长;
  • 高并发下成本较高。

所以 FLAT 更适合:

  • 小规模数据;
  • 离线评测;
  • 检查近似索引是否损失过多召回率。

3.3 HNSW:用图结构减少搜索范围

在大规模向量检索中,更常见的是 HNSW。

HNSW 会提前建立多层近邻图,查询时沿图搜索附近节点,而不是扫描全部向量。

Elasticsearch 的近似 kNN 搜索主要使用 HNSW;在当前版本中,普通浮点 dense_vector 默认使用量化后的 int8_hnsw,同时也允许根据场景配置其他 HNSW、Flat 和量化方案。

HNSW 中有三个非常重要的参数。

参数所处阶段参数增大后的主要影响
m建索引图连接更密,召回率可能提高,内存增加
ef_construction建索引建图质量提高,索引构建时间增加
num_candidates查询搜索候选增多,召回率提高,延迟增加

其中,m 和 ef_construction 主要决定索引图的质量;num_candidates 则决定一次查询愿意搜索多大的候选范围。

Elasticsearch 在每个分片中先获取一定数量的近似候选,再根据相似度选出 Top K,并合并各分片结果。

因此,HNSW 本质上是在做三者之间的平衡:

  • 查询速度;
  • 召回率;
  • 内存与索引成本。

3.4 量化不是另一套检索逻辑

在 Elasticsearch 中,我们还会看到:

  • int8_hnsw
  • int4_hnsw
  • bbq_hnsw
  • int8_flat
  • int4_flat

这些名称容易让人误以为它们是完全不同的检索算法。

实际上,它们更多是在回答另一个问题:

向量应该用多高的精度保存和参与检索?

原始浮点向量通常使用 32 位浮点数。量化可以把它压缩成 8 位、4 位甚至二进制表示,从而减少内存和磁盘消耗。

代价是:

  • 向量精度下降;
  • 相似度结果可能发生变化;
  • 召回率可能出现损失。

因此:

HNSW 负责怎样搜索,量化负责向量怎样压缩。

二者可以组合,但不是同一个概念。


3.5 Elasticsearch 支持哪些度量方式

Elasticsearch 的 dense_vector 支持多种相似度方式,包括:

  • cosine
  • dot_product
  • l2_norm
  • max_inner_product

普通浮点向量默认使用 Cosine;具体选择仍然应该以 Embedding 模型的训练方式和官方建议为依据。

对于 BGE-M3 这样的文本 Embedding,工程上不建议随意更换度量函数。

最稳妥的方式是:

  1. 确认模型输出是否已经归一化;
  2. 查看模型推荐的相关性计算方式;
  3. 使用评测集比较 Cosine 和 IP;
  4. 固定模型、向量版本和度量方式。

因为一旦更换 Embedding 模型或者度量方式,原有索引中的分数分布和排序结果都可能发生变化。


四、BGE-M3 为什么适合工业级混合检索

BGE-M3 的价值不只是生成一个稠密向量。

它可以在同一个模型中提供三种检索表示:

  • Dense Retrieval;
  • Sparse Retrieval;
  • Multi-Vector Retrieval。

同时,它支持多语言和长文本输入。


4.1 Dense Vector:解决语义相似

Dense Vector 更擅长理解整体语义。

例如用户问:

孩子晚上一直咳嗽,应该去看哪个科?

文档中写的是:

夜间持续性咳嗽建议优先就诊呼吸内科。

两段文字表面上的词并不完全相同,但是语义高度一致。

Dense Vector 能够把这种语义关系编码到连续向量空间中。

因此,它比较适合:

  • 自然语言问答;
  • 同义表达;
  • 问题改写;
  • 跨语言检索;
  • 用户问题和文档措辞不一致的场景。

4.2 Sparse Vector:保留词项,同时学习权重

BGE-M3 的 Sparse Vector 不是普通的固定长度稠密数组,而是一组非零 Token 与权重的对应关系。

例如:

{
  "设备": 0.82,
  "故障": 0.91,
  "处理": 0.46
}

真实数据中可能使用 Token ID,而不是直接使用中文词语,但核心思想相同。

Sparse Vector 保留了词项维度,又通过模型学习词项的重要性。

因此,它兼具一些关键词检索和语义检索的特点:

  • 保留较强的词项可解释性;
  • 能强化重要术语;
  • 能弱化无意义的常见词;
  • 可以实现一定程度的词项扩展;
  • 比单纯词频更能反映语义相关性。

Elasticsearch 的 sparse_vector 查询可以使用预计算的 Token—权重,并通过查询和文档稀疏向量的加权点积计算相关性。


4.3 Multi-Vector:更细粒度的 Token 交互

BGE-M3 还可以生成多向量表示。

Dense Retrieval 通常使用一个向量代表整段文本,而 Multi-Vector 会使用多个 Token 级向量表示文档。

查询时,可以让查询 Token 和文档 Token 进行更细粒度的匹配。

它的优势是相关性判断更加精细,但代价也更明显:

  • 向量数量更多;
  • 存储成本更高;
  • 查询计算更加复杂;
  • 不适合直接面对全库完成第一阶段召回。

因此,更常见的方式是:

BGE-M3 官方也推荐在 RAG 中采用混合召回加重排的检索链路。


五、为什么工业级 RAG 不能只依赖 Dense Vector

只使用 Dense Vector,看起来架构最简单:

但在真实业务中,它很容易遇到精确词召回问题。

例如:

  • 设备错误码 E1107
  • 交换机型号 H3C-S12500
  • 合同编号 HT20260725001
  • 国家标准 GB/T 19001-2016
  • 药品名称;
  • 人名;
  • 机构名;
  • 版本号;
  • 接口字段名。

这些信息的共同特点是:

它们的价值主要来自精确匹配,而不是整体语义。

Dense Vector 可能会召回一批“设备故障处理”“错误代码说明”“设备维修手册”等语义相近的文档,但不一定能保证精确命中 E1107

这正是 BM25 的优势。

另一方面,用户问题和文档可能使用完全不同的表达方式,这又是 Dense Vector 的优势。

因此,工业级 RAG 的重点不是争论 BM25 和 Dense 谁更强,而是让它们互相补足。


5.1 BM25、Dense 和 Sparse 的职责

检索方式主要优势典型场景
BM25精确词项、编号和专有名词错误码、型号、合同号、法规
Dense整体语义和同义表达自然语言问题、语义改写
Sparse学习型词项权重术语强化、语义词项扩展
Multi-VectorToken 级细粒度交互候选精排

一个比较合理的工业检索流程是:

这里每一层都有明确作用:

  • BM25 保证精确术语;
  • Dense 补充语义召回;
  • Sparse 提供学习型词项匹配;
  • RRF 合并不同分数体系;
  • Reranker 做最终相关性判断。

六、元数据过滤和 Milvus Partition 是一回事吗

这也是一个非常容易混淆的问题。

答案是:

元数据过滤和 Partition 不是一回事,但 Partition 可以帮助进一步缩小搜索范围。


6.1 元数据过滤解决业务条件问题

假设 Milvus Collection 或 Elasticsearch Index 中存储了以下字段:

  • vector
  • tenant_id
  • kb_id
  • department
  • doc_type
  • status

用户查询时,可以限定:

只搜索 tenant_001 中、属于 equipment_manual 知识库、状态为 active 的文档。

这就是元数据过滤。

它回答的是:

哪些数据符合业务条件?

Elasticsearch 可以在 kNN 查询中使用 Filter 进行预过滤,保证最终 Top K 候选同时满足元数据条件。

Milvus 同样支持基于标量字段的标准过滤和迭代过滤。


6.2 Milvus Partition 是数据组织范围

Milvus 中,一个 Collection 可以划分成多个 Partition。

例如,可以按业务或租户划分:

  • 租户一;
  • 租户二;
  • 租户三。

查询时,如果明确指定只搜索某个 Partition,就可以避免访问其他 Partition。

Milvus 还支持 Partition Key,根据某个标量字段的值自动组织数据;查询条件中包含 Partition Key 后,可以缩小需要搜索的 Partition 范围。


6.3 Partition 不等于 Filter

二者的区别可以这样理解:

概念作用
Metadata Filter根据字段表达式筛选符合条件的数据
Collection Partition把 Collection 划分为多个数据子集
Partition Key根据指定字段自动完成分区路由
IVF Cluster根据向量空间位置进行算法聚类

指定 Partition 后,Partition 内部仍然可以继续使用元数据 Filter。

例如:

此外,还要避免把 Milvus Partition 和 IVF 中的“向量聚类”混为一谈。

  • Collection Partition 按业务组织数据;
  • IVF Cluster 按向量空间位置组织数据;
  • Metadata Filter 按字段条件筛选数据。

三者虽然都在缩小搜索范围,但层次完全不同。


6.4 Elasticsearch 中对应什么

Elasticsearch 的普通元数据过滤,主要通过 Query DSL 中的 filter 完成。

例如:

  • tenant_id = tenant_001
  • status = active
  • department = maintenance

这些字段通常使用 keyworddate 或数值类型。

Elasticsearch 中没有和 Milvus Partition 完全相同的一对一概念。

比较接近的数据组织方式包括:

  • 不同业务使用不同 Index;
  • 通过 Shard 完成底层数据分布;
  • 使用 Custom Routing 将相同租户的数据路由到指定 Shard;
  • 查询时携带 Routing,减少需要访问的 Shard。

但对于大多数中小型企业 RAG,第一阶段通常不需要过早使用复杂 Routing。

更重要的是先保证:

  • tenant_id 过滤正确;
  • ACL 权限过滤正确;
  • 无效文档不会参与检索;
  • 不同知识库的数据不会混召回。

七、Elasticsearch 是不是已经把 Milvus 的能力都做了

从工业 RAG 的常见需求来看,两者的能力确实已经高度重叠。

能力ElasticsearchMilvus
稠密向量支持支持
稀疏向量支持支持
BM25传统强项已支持
元数据过滤支持支持
ANN 检索支持支持
精确向量检索支持支持
混合检索支持支持
多向量支持支持
分布式部署支持支持

Milvus 当前支持多种稠密向量索引、Sparse Vector、BM25 Function 和混合检索。

所以今天已经不能再简单地说:

Elasticsearch 只能做关键词,Milvus 只能做向量。

更准确的说法应该是:

Elasticsearch 是从搜索引擎向混合检索平台演化;Milvus 是从向量数据库向混合向量检索平台演化。

二者的能力出现了交叉,但它们的能力中心仍然不同。


7.1 Elasticsearch 的核心仍然是搜索

Elasticsearch 的能力体系是从下面这条路线发展而来的:

它天然擅长:

  • 全文检索;
  • 精确词匹配;
  • 短语查询;
  • 多字段加权;
  • 同义词;
  • 模糊查询;
  • 高亮;
  • 时间与数值范围查询;
  • 聚合统计;
  • 文档型数据检索
  • 日志与可观测性数据分析。

因此,Elasticsearch 是:

以文档搜索和复杂查询为中心,逐步增加向量能力。


7.2 Milvus 的核心仍然是向量

Milvus 的能力体系是从另一条路线发展而来的:

Milvus 支持 FLAT、HNSW、IVF_FLAT、IVF_PQ、DiskANN、GPU 索引和多种量化变体,并提供面向不同规模与内存条件的索引选择。

它天然更关注:

  • 大规模向量存储;
  • ANN 性能;
  • 多种向量索引;
  • 内存和磁盘之间的权衡;
  • GPU 加速;
  • 多模态向量;
  • 超大规模向量检索。

因此,Milvus 是:

以向量存储和 ANN 检索为中心,逐步增加文本和混合搜索能力。


八、什么时候只使用 Milvus

并不是所有 RAG 都需要 Elasticsearch。

如果业务本身就是向量优先,那么单独使用 Milvus 是合理的。


8.1 图片、视频和音频相似检索

例如:

  • 以图搜图;
  • 视频片段相似搜索;
  • 人脸特征检索;
  • 音频指纹检索;
  • 商品图片相似度;
  • 多模态推荐。

这些场景的核心不是关键词,而是高维特征向量之间的近邻关系。

即使增加关键词,它通常也只是辅助条件。


8.2 向量规模非常大

如果系统需要存储数亿、十亿甚至更大规模的向量,并且明确需要:

  • DiskANN;
  • IVF_PQ;
  • GPU_CAGRA;
  • GPU_IVF;
  • 更细致的量化策略;
  • 大于内存容量的数据搜索;

Milvus 的向量中心架构会更有优势。

DiskANN 等索引尤其适合数据规模超出内存容量的场景。


8.3 搜索条件相对简单

如果元数据主要是:

  • 租户;
  • 类别;
  • 时间;
  • 语言;
  • 状态;

同时不需要复杂的:

  • 同义词;
  • 短语匹配;
  • 搜索高亮;
  • 多字段权重;
  • 搜索运营规则;
  • 大量聚合分析;

那么 Milvus 加标量过滤可能已经足够。


九、什么时候只使用 Elasticsearch

对于大多数企业文档型 RAG,我更倾向于优先考虑 Elasticsearch。


9.1 企业知识库和文档检索

例如:

  • 医院知识库;
  • 工业设备手册;
  • 企业制度;
  • 智能客服;
  • 工单知识库;
  • 电商规则;
  • 产品说明书;
  • 法律法规;
  • 合同知识库。

这些场景通常同时需要:

  • 精确词匹配;
  • 语义搜索;
  • 元数据过滤;
  • 多租户隔离;
  • 权限过滤;
  • 文档溯源;
  • 多字段查询。

它们的核心仍然是文档搜索,而不只是向量近邻。


9.2 BM25 的价值不可替代

工业知识中经常出现:

  • 型号;
  • 错误码;
  • 接口名;
  • 文件名;
  • 合同号;
  • 人名;
  • 组织名;
  • 法律条款编号;
  • 药品名称。

这些内容通常不能只依赖 Dense Vector。

Elasticsearch 可以在同一个 Document 中保存:

  • 原始 Chunk 文本;
  • Dense Vector;
  • Sparse Vector;
  • 租户和权限字段;
  • 文档位置和来源。

然后一次完成:

这可以显著降低第一版系统的架构复杂度。


9.3 希望一个系统完成检索闭环

假设第一版同时使用 Elasticsearch 和 Milvus,就要面对:

  • 文档双写;
  • 向量双写或拆分写入;
  • 删除一致性;
  • 更新一致性;
  • ID 对齐;
  • 权限条件对齐;
  • 两套备份;
  • 两套监控;
  • 两套索引重建;
  • 跨引擎结果融合。

如果单 Elasticsearch 已经能够满足延迟、召回率和数据规模要求,那么双引擎并不会让系统显得更工业级。

很多时候,它只是让系统更复杂。


十、什么时候需要 Elasticsearch 加 Milvus

Elasticsearch 加 Milvus 并不是错误架构。

问题在于,必须存在明确理由。


10.1 Elasticsearch 的向量性能经过压测后不够

这里的关键词是:

经过压测后。

不能因为 Milvus 是专业向量数据库,就默认 Elasticsearch 的向量能力一定不够。

应该通过真实数据比较:

  • 向量数量;
  • 向量维度;
  • 查询并发;
  • P95、P99 延迟;
  • Recall@K;
  • 内存使用;
  • 索引构建时间;
  • 数据写入速度;
  • 过滤后的检索性能。

只有当测试证明 Elasticsearch 无法满足业务目标时,才需要拆分向量召回。


10.2 明确需要 Milvus 的专业索引能力

例如业务明确依赖:

  • DiskANN;
  • IVF_PQ;
  • GPU_CAGRA;
  • GPU_IVF;
  • 超大规模多模态向量;
  • 特殊的内存压缩方案。

这时可以让:

  • Elasticsearch 负责 BM25、全文检索和部分 Sparse 召回;
  • Milvus 负责大规模 Dense ANN;
  • 应用层统一完成 RRF 或自定义融合。


10.3 企业已经有两套成熟平台

如果企业内部已经存在:

  • 成熟的 Elasticsearch 搜索中台;
  • 成熟的 Milvus 向量平台;
  • 统一文档 ID;
  • 统一写入和删除事件;
  • 完整监控;
  • 备份和容灾;
  • 权限同步机制;

那么继续使用双引擎是合理的。

因为此时系统复杂度已经被平台能力消化。

但对于从零开发的项目,双引擎的建设成本通常不能被忽略。


10.4 双引擎不是免费的

一个文档更新事件,在双引擎架构中可能经历:

如果 Elasticsearch 成功、Milvus 失败,应该怎么办?

  • 重试 Milvus;
  • 回滚 Elasticsearch;
  • 标记文档为部分可用;
  • 暂停该知识库查询;
  • 使用旧版本向量;
  • 进入补偿任务队列。

删除文档时也会遇到同样问题。

所以:

双引擎不是“多部署一个数据库”那么简单,而是引入了分布式数据一致性问题。


十一、我的工业级 RAG 应该怎样设计

结合企业文档、BGE-M3、混合检索和 Agent 工作流,我认为第一阶段可以采用下面的架构。


11.1 MinIO 负责原始文件

MinIO 主要保存:

  • PDF;
  • Word;
  • PPT;
  • 图片;
  • 音频;
  • 原始上传文件;
  • 解析后的 Markdown;
  • 中间 JSON;
  • 页面截图;
  • 表格和图片产物。

不建议把完整 PDF 二进制直接塞进 Elasticsearch。

Elasticsearch 中只需要保存:

  • Chunk 文本;
  • 文档 ID;
  • 页码;
  • 段落位置;
  • MinIO 对象地址;
  • 检索字段;
  • 元数据;
  • 向量。

11.2 PostgreSQL 负责业务事实

PostgreSQL 或 MySQL 主要保存:

  • 用户;
  • 租户;
  • 知识库;
  • 文档任务;
  • 解析状态;
  • 文档版本;
  • 权限关系;
  • 上传记录;
  • 删除状态;
  • 评测任务;
  • Agent 任务信息。

关系型数据库仍然是业务事实来源。

Elasticsearch 是检索副本,不应该成为所有业务状态的唯一来源。


11.3 Elasticsearch 负责可检索数据

Elasticsearch 中的一条 Chunk,可以包含:

字段类别示例字段
标识chunk_id、doc_id、version_id
租户tenant_id、organization_id
知识库kb_id
文本title、content
向量content_dense、content_sparse
位置page_number、section_title
权限acl_users、acl_roles
状态status、effective_date
溯源source_uri、source_filename
模型版本embedding_model、parser_name

这样,BM25、Dense、Sparse 和权限过滤可以围绕同一条 Document 完成。


11.4 Redis 负责短期状态

Redis 可以承担:

  • Query Cache;
  • Embedding Cache;
  • 会话上下文;
  • Agent 中间状态;
  • 分布式锁
  • 任务进度;
  • 限流;
  • 热点问题缓存。

但 Redis 不适合作为完整知识库的长期事实来源。


11.5 LangGraph 负责复杂流程编排

当 RAG 进入 Agent 阶段以后,检索不再一定是固定的一次 Query。

一个 Agent 可能需要:

  1. 判断用户意图;
  2. 选择知识库;
  3. 查询业务接口;
  4. 执行多轮检索;
  5. 判断召回结果是否充分;
  6. 改写问题;
  7. 扩展关键词;
  8. 二次检索;
  9. 触发人工确认;
  10. 生成最终回答。

LangGraph 管理的是这条工作流,而 Elasticsearch 负责其中的检索能力。


十二、第一阶段为什么不建议直接加入 Milvus

对于一个企业文档型 RAG,第一阶段最重要的不是组件数量,而是把检索闭环跑通。

我更建议优先完成:

  1. 文档解析;
  2. Chunk 策略;
  3. Metadata 设计;
  4. BM25 召回;
  5. Dense 召回;
  6. Sparse 召回;
  7. RRF 融合;
  8. Reranker 精排;
  9. 权限过滤;
  10. 召回评测。

这套流程单独使用 Elasticsearch 就可以完成。

如果第一阶段直接引入 Milvus,很容易把大量时间消耗在:

  • 环境部署;
  • 双写代码;
  • 数据一致性;
  • 跨引擎融合;
  • 两套 Schema;
  • 两套监控;
  • 两套异常处理。

而真正决定 RAG 效果的内容可能还没有做好:

  • 文档是否正确解析;
  • Chunk 是否合理;
  • 问题是否需要改写;
  • Metadata 是否完整;
  • 权限是否准确;
  • 评测集是否真实;
  • Reranker 是否有效。

所以第一阶段应该优先验证检索方法,而不是优先堆叠基础设施。


十三、什么时候再引入 Milvus

Milvus 不应该因为“感觉更专业”而加入,而应该因为出现了可测量的瓶颈。

可以建立一套评测体系。

评测维度关注内容
Recall@K正确文档是否进入前 K 条
MRR正确文档排名是否足够靠前
NDCG多条相关文档的整体排序质量
P95 延迟大多数查询的响应时间
P99 延迟极端查询延迟
QPS单位时间可处理请求数
内存向量索引占用
磁盘原始向量与量化索引占用
构建时间全量重建索引需要多久
更新延迟文档更新多久后可检索

分别测试:

  • 纯 BM25;
  • 纯 Dense;
  • 纯 Sparse;
  • BM25 加 Dense;
  • BM25 加 Dense 加 Sparse;
  • RRF 后结果;
  • Reranker 后结果;
  • 不同 num_candidates
  • 不同 Chunk 长度;
  • 不同 Embedding 模型。

如果测试结果表明:

  • Elasticsearch 的 Dense 延迟无法满足要求;
  • 内存成本过高;
  • 向量规模超出预期;
  • 明确需要 DiskANN 或 GPU;
  • 向量检索已经成为独立平台;

再将 Dense ANN 拆分到 Milvus。

这时,Milvus 的加入是由数据驱动的,而不是由技术名词驱动的。


十四、最后的判断

重新理解 Elasticsearch 以后,我认为最重要的并不是记住 ES 和 Milvus 各自支持多少种索引,而是先把检索过程中的层次分清楚。

第一,索引结构和度量方式不是一回事。

  • 索引结构决定数据如何组织;
  • 搜索算法决定如何快速找到候选;
  • 度量方式决定候选之间谁更相似;
  • 元数据过滤决定谁有资格参与检索;
  • 排序和重排决定最终结果如何呈现。

第二,Elasticsearch 和 Milvus 的能力虽然越来越重叠,但能力中心不同。

  • Elasticsearch 以全文搜索、文档查询和复杂过滤为中心;
  • Milvus 以大规模向量存储和 ANN 检索为中心。

第三,不是所有工业级 RAG 都需要双引擎。

对于大多数企业文档知识库:

Elasticsearch 已经可以完成 BM25、Dense Vector、Sparse Vector、Metadata Filter、RRF 和结果重排前的候选召回。

第四,Milvus 应该在向量问题真正成为独立瓶颈时加入。

例如:

  • 向量规模非常大;
  • ES 向量检索经过压测后不够;
  • 需要 DiskANN、IVF_PQ 或 GPU 索引;
  • 企业已经具备成熟向量平台。

所以,我对工业级 RAG 架构选型的最终理解是:

不要从数据库名称出发设计架构,而要先从业务数据、检索方式、访问权限、性能目标和评测结果出发。

架构并不是组件堆得越多越专业。

真正合理的方式是:

先使用最小系统跑通完整检索闭环,再根据真实数据和压力测试决定是否拆分。

对于企业文档型 RAG,第一阶段从 Elasticsearch 开始,把 BM25、BGE-M3 Dense、BGE-M3 Sparse、元数据过滤、RRF 和 BGE-Reranker 跑通,通常是一条更稳、更容易评测,也更容易演进的路线。