Milvus 从零到面试:RAG 向量数据库原理、架构、索引与选型
一句话回答
Milvus 是一个面向大规模向量检索的开源、云原生数据库。它把向量、标量元数据和索引作为可持久化、可过滤、可扩缩容的数据来管理,适合为 RAG、语义搜索、推荐和多模态检索提供低延迟 ANN 召回。
面试时可以先给出下面这条主线:
文档切块 → Embedding → 写入 Milvus → 对 Query 做 Embedding → 向量 ANN + 元数据过滤/BM25 → 融合与 Rerank → 组装上下文 → LLM 生成。
需要先强调一个容易加分的结论:
RAG 不一定必须使用专用向量数据库。 小规模、静态、单机数据可以用 NumPy/Faiss;已有 PostgreSQL 可以先用 pgvector;已有 Elasticsearch 且强依赖全文检索时可以直接做混合搜索。只有当数据量、并发、过滤、实时更新、持久化、高可用或独立扩缩容需求上来后,专用向量数据库的价值才明显。
为什么需要“向量数据库”
这个问题最好从传统关系数据库擅长什么讲起。
MySQL、PostgreSQL 等关系数据库主要面向结构化数据和精确查询。一条典型 SQL 可能是:
1 | SELECT * |
这类查询的条件是明确的:字段等于什么、是否在某个范围、两张表如何关联。关系数据库可以通过 B+ Tree、Hash、Bitmap 等索引快速定位记录,并提供事务、约束、Join 和聚合能力。
但 RAG 面对的问题通常是:
给定“员工离职后邮箱还能用多久?”,从几十万或上百万个文本块中找出语义最接近的若干段内容。
数据库中可能存的是“劳动关系终止后,企业账户保留七日”。这两段文本没有足够的精确字段关系,=、范围查询和普通 B+ Tree 都无法表达“语义上是否相似”。LIKE '%离职%' 或全文检索能够匹配词面,却不一定能处理同义改写、口语化问题和跨语言表达。
因此,两类数据库解决问题的出发点不同:
| 对比维度 | 传统关系数据库 | 向量数据库 |
|---|---|---|
| 主要数据 | 数字、字符串、日期等结构化字段 | Embedding 向量及其标量元数据 |
| 查询目标 | 找到满足确定条件的记录 | 找到距离最近、语义最相似的 Top-K |
| 匹配方式 | 等值、范围、Join、聚合 | COSINE、IP、L2 等距离计算 |
| 常用索引 | B+ Tree、Hash、Bitmap | HNSW、IVF、PQ、DiskANN |
| 返回语义 | 满足条件或不满足条件 | 按相似度排序的近似结果 |
| 典型优势 | ACID 事务、复杂 SQL、数据完整性 | 高维向量 ANN、Recall-Latency 权衡、大规模并行召回 |
| RAG 中的角色 | 保存用户、权限、订单、文档状态等业务事实 | 保存 Chunk Embedding 并完成语义召回 |
为什么普通索引不适合向量检索
B+ Tree 依赖一维有序关系。例如数字 10 < 20 < 30,可以通过树结构快速排除大量不相关数据。但一个 768 维向量不存在这样简单的全局顺序,而且还可能需要分别按照 COSINE、IP 或 L2 判断距离。
假设知识库有 $N$ 个 Chunk,每个向量维度为 $d$。如果只把向量作为数组或 BLOB 存进关系数据库,而没有专用向量索引,那么每次检索都需要:
- 读取大量甚至全部向量;
- 对 Query 与每个向量计算距离;
- 对结果排序并取 Top-K。
暴力检索的计算复杂度约为:
当 $N$ 很小时这完全可接受;当数据达到百万、亿级且并发升高时,每次全量扫描代价过高。向量数据库使用 ANN 索引,只访问更可能包含近邻的桶、图节点或磁盘区域,以可控的少量 Recall 损失换取显著更低的延迟。
所以,向量数据库主要解决的不只是“存一个浮点数组”,而是以下问题:
- 高效 ANN 检索:通过 HNSW、IVF、DiskANN 等索引减少候选范围;
- 持久化和增量更新:支持 Insert、Upsert、Delete、Compaction,而不是每次重建一个内存索引;
- 标量过滤:在
tenant_id、权限、时间、语言、文档类型等条件下做向量搜索; - 分布式扩展:数据分片、查询并行、负载均衡、故障恢复;
- 一致性选择:控制“刚写入的数据多久能被搜索到”;
- 多租户与安全:数据库、Collection、Partition、RBAC、TLS;
- 运维能力:监控、备份、限流、资源隔离、滚动升级;
- 混合检索:在同一系统中管理 Dense、Sparse/BM25 和多向量字段。
关系数据库真的不能做向量检索吗
不能绝对地说关系数据库不能做。PostgreSQL 安装 pgvector 后,也能定义向量类型并使用 HNSW、IVFFlat 等索引:
1 | SELECT chunk_id, content |
这时 PostgreSQL 已经通过扩展获得了专门的向量存储、距离运算和 ANN 索引能力。对于数据量不大、QPS 不高、团队已有 PostgreSQL,并且需要事务和 Join 的项目,pgvector 往往比新增 Milvus 更简单。
专用向量数据库的价值主要在于向量成为核心工作负载以后:
- 查询、写入、索引构建和 Compaction 可以独立扩缩容;
- 提供更多向量索引、压缩、磁盘和 GPU 路径;
- 面向大量向量做分片、并行检索和全局 Top-K 归并;
- 原生支持多向量、Dense + Sparse/BM25、过滤和 Recall-Latency 调优;
- 避免大规模向量查询与关系数据库中的 OLTP 事务争抢 CPU、内存和 I/O。
因此,选择向量数据库并不是因为关系数据库“不能存向量”,而是因为两者的主要优化目标不同。
面试回答可以概括为:
传统关系数据库擅长结构化数据上的精确匹配、事务和 Join,但 RAG 需要在高维向量中按照 COSINE、IP 或 L2 找语义最相似的 Top-K。普通 B+ Tree 无法有效支持这种高维近邻查询,只把向量存成数组会退化为全量距离计算。向量数据库通过 HNSW、IVF、DiskANN 等 ANN 索引,以及分片、并行查询、标量过滤和增量更新,把语义检索做成可扩展的在线数据库能力。不过,如果数据量较小且已有 PostgreSQL,使用 pgvector 可能更经济;只有当向量成为主要工作负载时,Milvus 这类专用系统的优势才明显。
2. 向量检索基础
2.1 Embedding 是什么
Embedding 是把文本、图片、音频等对象编码为稠密向量:
训练良好的 Embedding 模型会让语义相似对象的向量更接近。数据库不理解自然语言语义,它只负责存储模型生成的向量,并根据指定的距离函数检索。
三个工程约束必须记住:
- 写入文档和查询时必须使用兼容的 Embedding 模型;
- 向量维度必须与 Collection Schema 一致;
- 切换 Embedding 模型通常意味着重新生成全量向量并重建索引。
因此生产系统应保存 embedding_model、embedding_version、chunk_version,不能只保存向量。
2.2 常见距离度量
余弦相似度 COSINE
只关注方向,通常适合文本语义向量。值越大越相似。
内积 IP
同时受方向和模长影响,值越大越相似。如果向量已经做单位归一化,则 IP 与 COSINE 的排序等价。
欧氏距离 L2
值越小越相似。常见于图像、几何或模型明确要求使用 L2 的场景。
| 度量 | 越大/越小越相似 | 常见场景 | 注意点 |
|---|---|---|---|
| COSINE | 越大 | 文本语义检索 | 忽略模长 |
| IP | 越大 | 已归一化向量、推荐 | 未归一化时模长会影响结果 |
| L2 | 越小 | 图像、几何距离 | 与模型训练目标保持一致 |
最重要的工程规则不是“文本一律用 COSINE”,而是:
优先遵循 Embedding 模型的 Model Card;建索引和搜索时使用相同 metric。
2.3 KNN、ANN、Recall
- KNN/Exact Search:遍历所有向量,结果准确,计算量大;
- ANN/Approximate Nearest Neighbor:通过索引只检查部分候选,速度快,但可能漏掉真正近邻;
- Top-K:返回最相似的 K 个结果;
- Recall@K:真实相关结果中有多少出现在前 K 个结果中。
若真实 Top-10 中有 8 个被 ANN 找到,则:
ANN 的核心权衡是:
1 | 更深地搜索索引 → Recall 更高 → 延迟和 CPU 开销更大 |
向量数据库调优不能只看 QPS,也不能只看 Recall,通常要同时看:
- Recall@K / NDCG@K / MRR;
- P50、P95、P99 延迟;
- QPS;
- 索引构建时间;
- 内存、磁盘和网络开销;
- 数据写入到可检索的延迟。
3. Milvus 的数据模型
3.1 核心概念
1 | Milvus Instance |
| 概念 | 类比关系数据库 | 说明 |
|---|---|---|
| Database | Database | 逻辑隔离多个业务 |
| Collection | Table | 一组 Schema 相同的 Entity |
| Entity | Row | 一条记录,例如一个 Chunk |
| Field | Column | 主键、向量、VARCHAR、JSON、时间等 |
| Partition | Partition | 按业务或租户缩小检索范围 |
| Segment | 存储/执行单元 | Milvus 内部组织、索引和查询数据的基本单元 |
| Index | Index | 向量 ANN 索引或标量索引 |
一个 Collection 必须有主键,可以包含多个向量字段和多个标量字段。RAG 中一般是一条 Entity 对应一个 Chunk,而不是一整篇 Document。
3.2 Growing Segment 与 Sealed Segment
Milvus 写入的数据先进入增长中的 Segment:
- Growing Segment:仍在接收实时写入,通常以较接近暴力扫描的方式参与查询;
- Sealed Segment:不再写入,可构建完整索引并由 Query Node 加载查询;
- Compaction:合并小 Segment、清理删除数据和降低碎片。
这解释了两个常见现象:
- 刚写入的数据虽然能被搜索,但性能特征可能与已建索引的历史数据不同;
- 高频小批量写入和删除会产生碎片,Compaction、批量写入策略与 Segment 状态会影响 P99 延迟。
3.3 RAG 推荐 Schema
1 | chunk_id VARCHAR / primary key |
设计原则:
- 主键要稳定、可重建,常用
hash(document_id + chunk_no + version); - 高频过滤字段显式定义并建立标量索引;
- 灵活但低频的元数据可以放 JSON 或动态字段
$meta; tenant_id、权限和is_active必须在召回时过滤,不能只在返回后过滤;- 原文、页码、标题、URL 等引用信息要与向量放在同一 Entity 或可通过主键稳定回表;
- 大对象不要直接塞入一条记录,可只存 Chunk 和对象存储 URI。
4. Milvus 2.6.x 架构
4.1 总体设计
Milvus Distributed 的核心思想是:存储与计算分离、控制面与数据面分离、查询/写入/离线任务可独立扩缩容。
1 | ┌──────────────┐ |
Access Layer:Proxy
- 对外提供统一访问入口;
- 校验请求并做路由;
- 将查询分发到多个节点;
- 对各分片 Top-K 结果做多级归并;
- 本身无状态,可通过负载均衡横向扩展。
Coordinator
- 管理 Collection、Partition、Index 等元数据操作;
- 管理时间戳和一致性;
- 调度 Query Node、Data Node、Streaming Node;
- 负责拓扑、负载均衡、Segment 和任务管理。
Streaming Node
- 处理实时写入路径;
- 将操作记录到 WAL;
- 查询尚未转为历史数据的 Growing Segment;
- 推进数据由 growing 到 sealed 的状态转换。
Query Node
- 从对象存储加载历史 Segment 和索引;
- 执行 Sealed Segment 上的向量检索与标量过滤;
- 并行计算局部 Top-K。
Data Node
- 执行历史数据的离线处理;
- 构建索引;
- 执行 Compaction;
- 将处理结果写入对象存储。
Storage
- 元数据存储:保存 Schema、拓扑、检查点等,需要强一致和高可用;
- WAL:保证实时写入的持久性和故障恢复;
- 对象存储:保存 Binlog、索引和历史数据,常用 S3/MinIO。
面试不要只背组件名,要说明为什么拆分:
RAG 的查询、写入和索引构建资源模型不同。查询偏 CPU/内存,索引构建是离线计算,写入依赖日志和实时可见性。拆分后可以针对瓶颈独立扩容,也能让计算节点更接近无状态,方便故障恢复。
4.2 写入链路
1 | Client Insert/Upsert |
insert 不应被理解成“每一行同步写入最终索引”。Milvus 是面向批量、分段和异步索引构建的系统。生产中应尽量批量写入,避免每条数据单独请求。
4.3 查询链路
1 | Query → Embedding |
分布式 Top-K 不能简单让每个分片只返回 1 条。一般需要每个相关分片返回局部 Top-K,再进行全局归并,否则可能漏掉全局最优结果。
5. Milvus 索引:面试重点
5.1 FLAT
原理:遍历全部向量并计算真实距离。
- 优点:Recall 为 100%,无 ANN 误差,几乎不需要训练索引;
- 缺点:复杂度接近 $O(Nd)$,数据大或并发高时慢;
- 适用:小数据集、离线评测 Ground Truth、强过滤后候选极少、极高 Recall 场景。
5.2 IVF_FLAT
IVF(Inverted File)先通过聚类把向量划分到 nlist 个桶,查询时只搜索离 Query 最近的 nprobe 个桶。
1 | 全部向量 |
关键参数:
nlist:桶的数量;太少则每个桶太大,太多则训练与管理成本增加;nprobe:查询时探测多少个桶;越大 Recall 越高、延迟越大。
IVF_FLAT 桶内仍保存原始向量,精度通常好,但内存占用不低。
5.3 IVF_SQ8 与 IVF_PQ
量化通过牺牲部分精度减少内存:
- SQ8:把每个浮点维度量化为 8 bit;
- PQ:把向量拆成多个子向量,分别用码本编码,压缩更强。
特点:
- IVF_PQ 内存小,适合大规模和资源受限场景;
- 压缩会引入距离估计误差;
- 可以扩大候选后用原始向量 Refinement,换取更高精度。
5.4 HNSW
HNSW(Hierarchical Navigable Small World)构建多层近邻图:
- 上层节点少,用于快速跳转到目标区域;
- 下层节点多,用于局部精细搜索;
- 查询像“从高速公路进入城市道路”。
关键参数:
M:每个节点的连接度;越大通常 Recall 越高、内存和构建成本越大;efConstruction:建图时的候选宽度;越大索引质量越高、构建越慢;ef:查询时的候选宽度;越大 Recall 越高、查询越慢,且应不小于 Top-K。
优点:高 Recall、低延迟、非常适合较小 Top-K。缺点:图结构内存开销大、构建相对慢。
5.5 DiskANN
DiskANN 使用适合 SSD 的图索引思路,使大部分索引/原始数据不必全部常驻内存。
- 优点:数据超出内存时仍能保持较好的延迟和 Recall;
- 缺点:依赖高性能 NVMe SSD,对磁盘随机读和缓存命中更敏感;
- 适用:亿级、十亿级数据,内存成本成为主要瓶颈的场景。
5.6 Sparse Index 与 BM25
Milvus 支持 SPARSE_FLOAT_VECTOR 和倒排索引,可用于:
- Milvus 内置 BM25 Function;
- SPLADE、BGE-M3 等模型生成的 Learned Sparse Embedding;
- Dense + Sparse Hybrid Search。
BM25 更关注词频、逆文档频率和文档长度归一化:
对错误码、专有名词、数字和型号,Sparse/BM25 往往能补足 Dense 检索的盲区。
5.7 如何选索引
| 场景 | 优先尝试 | 原因 |
|---|---|---|
| 小数据或需要 Ground Truth | FLAT | 准确、简单 |
| 数据在内存、低 Top-K、高 Recall | HNSW | 查询速度和召回率通常较好 |
| 内存较紧、可接受轻微精度损失 | IVF_SQ8 / IVF_PQ | 压缩明显 |
| 大 Top-K 或适合聚类裁剪 | IVF 系列 | 可通过nprobe 控制扫描范围 |
| 数据超过内存且有 NVMe | DiskANN | 降低内存压力 |
| 关键词/BM25 | SPARSE_INVERTED_INDEX | 稀疏倒排检索 |
| 不想先决定具体索引 | AUTOINDEX | 先建立基线,再根据评测显式调优 |
正确的选型方式是用自己的数据做 Recall-Latency 曲线,不是背一个“百万数据就一定用 HNSW”的固定答案。数据分布、维度、Top-K、过滤比例、并发和硬件都会改变结果。
6. 标量过滤、分区和多租户
6.1 为什么过滤必须在召回阶段执行
错误做法:先向量召回 Top-10,再在应用层删除无权限数据。
假设 Top-10 中 9 条属于其他租户,过滤后只剩 1 条;但索引中可能还有大量属于当前租户的相关文档,只是它们没有机会进入原始 Top-10。这叫后过滤截断。
正确做法:
1 | filter='tenant_id == "acme" and is_active == true and language == "zh"' |
权限过滤必须进入所有召回路径,包括 Dense、Sparse/BM25 和任何缓存层。
6.2 标量索引
Milvus 除了向量索引,也支持标量字段索引,例如:
- VARCHAR/数字字段:INVERTED;
- BOOL、低基数或数组:BITMAP;
- JSON:对常用 JSON Path 建索引;
- 动态字段
$meta:可保存未显式声明的字段,但高频过滤字段更适合固定 Schema。
没有标量索引不代表不能过滤,但可能退化为更昂贵的扫描。
6.3 Partition 与 Partition Key
- 手动 Partition:显式把数据分到不同 Partition,可独立 Load/Release;
- Partition Key:按某个字段的值自动路由到物理 Partition;
- 带 Partition Key 等值条件的查询可以减少无关数据扫描。
不要为每个小用户无限创建手动 Partition。租户很多时,Partition Key 通常比“一租户一 Partition”更可扩展。
6.4 多租户四种粒度
| 方式 | 隔离性 | Schema 灵活度 | 规模 | 典型场景 |
|---|---|---|---|---|
| Database per tenant | 强 | 高 | 租户较少 | 强合规、独立权限 |
| Collection per tenant | 强 | 中高 | 中等 | 各租户 Schema/索引不同 |
| Partition per tenant | 较强 | 共享 Schema | 中等 | 需要独立 Load/Release |
| Partition Key | 逻辑隔离为主 | 共享 Schema | 最大 | 大量小租户 |
多租户选择不只看数量,还要看:RBAC、跨租户查询、冷热数据、Schema 差异和资源隔离。高安全场景不能仅依赖应用层 tenant_id 过滤。
7. 一致性:为什么刚写入的数据可能搜不到
Milvus 是分布式、存算分离系统。写入先进入 WAL 和实时链路,查询节点维护自己的可服务时间。搜索时需要在“更新鲜的数据”和“更低的延迟”之间权衡。
Milvus 提供四种一致性级别:
| 一致性 | 含义 | 适用场景 |
|---|---|---|
| Strong | 搜索等待到最新写入可见 | 写后立即读、更新后必须马上查到 |
| Session | 同一客户端会话看到自己的写入 | 交互式应用 |
| Bounded Staleness | 允许有界陈旧,默认级别 | 大多数 RAG 在线检索 |
| Eventually | 不等待最新数据传播 | 最看重延迟、允许短暂旧数据 |
面试回答:
Strong 不等于检索更“相关”,它只保证数据可见性更新鲜;代价可能是 Query Node 等待进度追上,导致更高延迟。离线批量构建知识库可用 Bounded/Eventual,用户上传后立刻问答则可考虑 Session 或 Strong。
一致性、索引 Recall 和业务正确性是三件不同的事:
- 一致性解决“最新写入是否可见”;
- ANN Recall 解决“近邻是否被索引找到”;
- RAG 正确性还取决于切块、Embedding、Rerank 和 LLM。
8. 用 Milvus 构建完整 RAG
8.1 离线写入流程
1 | 数据源 |
Chunking 原则
- Chunk 太小:语义不完整,答案可能跨块;
- Chunk 太大:噪声变多,Embedding 语义被稀释,Prompt 成本上升;
- 固定字符切分可能截断标题、表格和代码;
- 可以按标题和段落切分,必要时保留适度 overlap;
- 用子块检索、返回父块是一种常见 Parent-Child Retrieval;
- 同一文档召回多个相邻块时,要去重并控制上下文垄断。
8.2 在线查询流程
1 | 用户 Query |
为什么需要 Reranker
Dense 检索通常是 Bi-Encoder:Query 和 Document 分别编码,可预计算文档向量,因此很快;但它只能通过单个向量粗略表达相关性。
Cross-Encoder Reranker 把 Query 和候选 Document 一起输入模型:
它能做更细粒度的词语对齐和上下文判断,但不能对整个知识库逐条推理,所以应先用 Milvus 把候选从百万级缩到几十或几百条,再精排。
8.3 RRF 融合
不同检索器的分数尺度不同,BM25 可能是几十,COSINE 通常在一个较小区间,不能直接相加。RRF 只使用排名:
优点是无需校准原始分数,适合作为强基线。限制是丢弃了原始置信度,并默认各路召回同等重要。
9. 从零运行一个 Milvus Lite Demo
Milvus 有三种主要部署方式:
- Milvus Lite:嵌入 Python 进程,适合本地学习和小规模原型;
- Milvus Standalone:Docker 单机服务,适合开发测试和中小规模生产;
- Milvus Distributed:Kubernetes 分布式集群,适合大规模、高可用和独立扩缩容。
下面用 Milvus Lite 跑通“文本 → 向量 → 入库 → 过滤检索”。
9.1 安装
1 | pip install -U "pymilvus[milvus-lite]" sentence-transformers |
Milvus Lite 官方支持的本地平台主要是 Ubuntu 和 macOS;Windows 用户可在 WSL2 中运行本例,或改用 Docker 部署 Milvus Standalone。
9.2 完整代码
1 | from hashlib import sha256 |
如果切换到 Standalone/Distributed,核心业务代码不需要重写,只需替换连接方式:
1 | client = MilvusClient( |
注意:Milvus Lite 用于学习和小规模原型,不代表 Distributed 的性能、组件和容灾能力。Lite 当前仅使用 FLAT 向量索引、只提供 Strong Consistency,也不支持 Partition 和 RBAC;过滤表达式可以运行,但不能用它验证分布式标量索引性能。
10. Dense + BM25 混合检索示例
以下代码展示核心结构,Embedding 生成部分省略。BM25 Function 会把 content 自动转换为 Sparse Vector。
1 | from pymilvus import ( |
这里的 RERANK 是 Milvus 内部对多路检索结果做 RRF/加权融合,不等同于 Cross-Encoder 模型精排。常见完整流程仍然是:
1 | Milvus Dense + BM25 → RRF Top-50 → 外部 Cross-Encoder → Top-5 → LLM |
11. Insert、Upsert、Delete、Flush、Load
11.1 Insert 与 Upsert
insert用于新增;不要假设它会为你检查主键重复;upsert根据主键插入或替换,适合文档更新和幂等写入;- 更新 Embedding 时应同步更新
content_hash、模型版本和时间; - 文档级更新要避免旧 Chunk 残留,可以先写新版本、切换
is_active,再延迟删除旧版本。
一个稳妥的版本切换模式:
1 | 写入 document_version = v2 |
这比“先删旧数据再写新数据”更容易避免知识库空窗。
11.2 Flush
flush() 强制推进数据持久化,但不应在每次小批量 Insert 后调用。频繁 Flush 会制造过多小 Segment,增加索引和 Compaction 压力。
11.3 Load 与 Release
- Collection/Partition 需要加载后才能高效参与查询;
- Load 把所需历史数据和索引加载到查询资源;
- Release 释放查询资源,不等于删除持久化数据;
- 热数据保持加载,冷数据可按业务和 Partition 管理。
12. 为什么选择 Milvus,而不是别的向量数据库
先给结论:没有对所有场景都最好的向量数据库,只有与现有技术栈、数据规模和团队能力最匹配的选择。
12.1 对比表
| 方案 | 更适合什么 | 相对 Milvus 的优势 | 相对 Milvus 的不足/取舍 |
|---|---|---|---|
| Faiss | 单机算法实验、离线索引、GPU 检索 | 底层算法丰富、轻量、控制力强 | 是检索库而非完整数据库;持久化、增量更新、过滤、分布式、高可用要自己做 |
| pgvector | 已有 PostgreSQL,中小规模,强事务/Join | 数据和业务表共库、SQL/事务/生态成熟、运维简单 | 专用向量工作负载与独立扩缩容能力较弱;大规模向量可能与 OLTP 争资源 |
| Elasticsearch | 已有 ES,全文搜索、日志/搜索平台 | BM25、文本分析器、聚合和搜索生态强,混合搜索自然 | 纯向量大规模成本与专用调优可能不是最优;ES 集群本身也复杂 |
| Qdrant | 想要较轻量服务、强 payload filtering | API 简洁、过滤体验好、部署相对直接、支持分布式和混合查询 | 在超大规模存算分离、索引/硬件选择广度上需结合实测比较 |
| Weaviate | 快速 AI 应用、模块化向量化、多租户 | 开箱即用的 BM25 Hybrid、模块和对象模型友好 | 自托管集群仍有运维成本;架构、资源模型与 Milvus 不同,需要用真实负载验证 |
| Pinecone | 不想自建、全托管 Serverless | 运维负担小、快速上线、按服务使用 | 闭源托管、供应商绑定、成本和数据合规需评估,底层控制较少 |
| Chroma | 本地原型、教学、开发者体验 | 上手非常快,可嵌入本地;也有 Cloud/Distributed 路线 | 本地模式不等同于大型生产集群;生产能力要按具体部署模式评估 |
| Milvus | 大规模专用向量检索、独立扩缩容、多索引/多向量 | 云原生存算分离、索引类型丰富、Dense/Sparse/BM25、GPU/磁盘索引、开源 | 分布式自托管组件多、资源和运维门槛高;小项目可能过重 |
12.2 为什么不用 Faiss
Faiss 是高性能向量检索库,Milvus 的很多向量索引能力也建立在成熟 ANN 库之上。但 Faiss 不直接解决:
- 分布式分片和副本;
- WAL、故障恢复和在线增量更新;
- 标量元数据过滤;
- 多租户、RBAC、TLS;
- 在线服务、限流、监控、备份;
- 多节点 Top-K 归并。
因此:离线实验或单机百万级静态索引用 Faiss 很合理;要把它做成生产数据库,需要自行补齐大量工程能力。
12.3 为什么不用 pgvector
如果公司已经有 PostgreSQL,而且只有几十万到几百万向量、QPS 不高,还需要复杂事务和 Join,pgvector 往往是最务实的第一选择:
1 | SELECT id, content |
选择 Milvus 的理由通常不是“PostgreSQL 不能搜向量”,而是:
- 希望向量检索和 OLTP 解耦,独立扩缩容;
- 数据达到更大规模,索引和内存成为主要成本;
- 需要 DiskANN、GPU、多向量、Sparse/BM25 等更专用能力;
- 查询、写入、索引构建需要独立资源池;
- 可接受额外系统和数据同步成本。
pgvector 的 ANN + 过滤要特别测试 Recall。其 HNSW/IVFFlat 在过滤时可能需要 Iterative Scan、Partial Index 或分区才能返回足够候选。
12.4 为什么不用 Elasticsearch
如果核心需求是站内搜索、复杂分词、短语匹配、字段加权、聚合和关键词检索,Elasticsearch 很强,而且已支持向量 KNN 和 Hybrid Search。
选择 Milvus 的典型理由:
- 向量是主工作负载而不是附加字段;
- 数据和 QPS 需要专用向量集群扩展;
- 更看重 ANN 索引多样性、磁盘/GPU 路径和多向量检索;
- 团队愿意让 ES 负责文本搜索、Milvus 负责向量召回,再在应用层融合。
但如果团队已经稳定运行 ES,新增 Milvus 会引入双写、双索引一致性和额外运维,未必值得。
12.5 为什么不用 Qdrant 或 Weaviate
这两者都不是“不能用于生产”的轻量玩具,也支持过滤、分布式和混合检索。合理比较应落在真实需求:
- 数据规模和增长速度;
- 单租户大 Collection 还是大量小租户;
- Dense、Sparse、BM25、多模态、多向量需求;
- 过滤表达式和过滤比例;
- 内存、SSD、GPU 预算;
- 自托管复杂度和团队熟悉度;
- P95/P99、Recall、写入吞吐和恢复时间。
选择 Milvus 可以强调其云原生存算分离、丰富索引和大规模扩展路线;选择 Qdrant/Weaviate 则可能更看重 API 体验、特定过滤/多租户模型或更简单的起步。最终必须用同一批数据和同一 Embedding 做基准测试。
12.6 为什么不用 Pinecone
Pinecone 是全托管方案,优势是无需管理底层集群,适合快速上线。选择自建 Milvus 的原因可能是:
- 私有化、离线或数据主权要求;
- 希望掌控索引、资源、硬件和版本;
- 大规模稳定负载下,自建成本模型更合适;
- 避免供应商绑定。
反过来,如果团队没有向量数据库运维能力,Pinecone 或托管 Milvus/Zilliz Cloud 可能比自建更可靠。
12.7 什么时候不该选 Milvus
- 只有几万条静态向量;
- 现有 PostgreSQL/Elasticsearch 已满足延迟和 Recall;
- 强依赖跨表事务、复杂 Join;
- 团队没有 Kubernetes、对象存储和分布式系统运维能力;
- 无法承担业务数据与向量库之间的同步复杂度;
- 使用全托管服务更符合成本和合规要求。
面试中的高质量结论:
我选择 Milvus 不是因为它在所有维度都最好,而是因为项目的主要矛盾是大规模向量召回、独立扩缩容、过滤和混合检索;如果项目规模小且数据本来就在 PostgreSQL,我会先用 pgvector,避免过度设计。
13. 生产调优
13.1 先建立评测集
至少包含:
- 同义改写和自然语言问题;
- 错误码、型号、数字、人名;
- 否定、比较、时间范围;
- 权限和租户过滤;
- 热门 Query 与长尾 Query;
- 有答案、无答案、多答案;
- 文档刚更新和刚删除的场景。
每个 Query 标注相关 Chunk 和相关性等级,再分别评估 Dense、BM25、Hybrid、Rerank,而不是只看最终 Demo 是否“感觉不错”。
13.2 分阶段调参
- 固定 Chunking 和 Embedding,测 FLAT 得到 Ground Truth;
- 比较 HNSW、IVF、DiskANN 的 Recall-Latency 曲线;
- 调
ef、nprobe等搜索深度; - 加入真实过滤条件重新评测;
- 调每路 Top-N、RRF/权重与 Rerank 候选数;
- 最后做端到端答案评测和压测。
不要同时修改 Chunk 大小、Embedding 模型、索引和 Reranker,否则无法判断收益来自哪里。
13.3 内存估算
仅原始 FP32 向量的大小约为:
例如 1 亿条、1024 维 FP32:
这还没有包括:
- HNSW 图边;
- 主键和标量字段;
- Segment、缓存和执行临时内存;
- 副本;
- 操作系统和进程开销。
副本数为 2 时,查询侧数据占用不能简单仍按一份估算。生产容量规划应保留余量,并考虑热点 Collection 同时加载。
13.4 常见性能问题
Recall 低
- Embedding 模型不适合领域或中英文不匹配;
- 文档和 Query 使用了不同模型/版本;
- 距离度量不匹配;
- Chunking 错误;
ef/nprobe太小;- PQ 压缩过强;
- 过滤条件导致候选不足;
- Top-K 太小;
- Dense 本来就不擅长精确词,应加入 BM25。
延迟高
- 查询节点内存不足、频繁从磁盘加载;
- HNSW
ef或 IVFnprobe过大; - 过滤字段无索引;
- 返回过多大字段;
- Segment 过碎、Compaction 压力大;
- 热点租户或 Collection 资源竞争;
- 多分片结果归并和网络开销大;
- Query Embedding/Reranker 本身慢,误以为是 Milvus 慢。
写入慢或数据迟迟不可查
- 每条数据单独 Insert,批量太小;
- 频繁 Flush;
- 索引构建/Compaction 资源不足;
- 一致性级别与实时可见性预期不一致;
- 写入速率超过 Streaming/Data 节点处理能力。
14. 高可用、安全和运维
14.1 高可用不是“有副本就结束”
需要分别考虑:
- Proxy 多副本和负载均衡;
- Coordinator 故障转移;
- Query Node 副本与重新加载时间;
- WAL 和对象存储可靠性;
- etcd 高可用与备份;
- 节点宕机后 Segment 重分配;
- 跨可用区网络和对象存储延迟;
- 恢复时是否还能满足 P99。
14.2 安全
- 启用认证,不使用默认弱口令;
- 配置 TLS;
- 使用 RBAC 和最小权限;
- 租户隔离不能只靠客户端传入过滤条件;
- 敏感原文考虑加密、脱敏和审计;
- 备份与对象存储也要有访问控制;
- 删除数据时同时考虑主库、Milvus、缓存、备份和离线文件。
14.3 监控指标
至少关注:
- 搜索 QPS、P50/P95/P99、错误率和超时;
- Insert/Upsert 吞吐;
- 写入到可见的延迟;
- Segment 数量、Growing/Sealed 状态;
- Index Build、Load、Compaction 队列;
- Query/Data/Streaming 节点 CPU、内存、磁盘和网络;
- 对象存储错误和延迟;
- Recall 离线趋势与线上零结果率;
- 各租户资源使用和热点分布。
只有系统指标没有检索质量指标是不够的:数据库可以 10 ms 返回完全不相关的结果。
15. 常见面试题与参考答案
15.1 为什么 RAG 需要向量数据库
RAG 要从大量非结构化文档中找到语义相关证据。Embedding 把文本映射为向量,向量数据库通过 ANN 索引降低全量距离计算成本,并提供持久化、增量更新、元数据过滤、多租户、高可用和扩缩容。小规模 RAG 不一定需要专用向量数据库,数据量和工程需求上来后才体现价值。
15.2 Milvus 与 Faiss 有什么区别
Faiss 是向量相似度检索算法库;Milvus 是数据库。Faiss 提供索引和搜索,但分布式、持久化、实时写入、标量过滤、权限、高可用和运维通常要自己实现。Milvus 把这些能力组合成在线服务。
15.3 为什么 Milvus 要存算分离
向量查询、写入、索引构建和 Compaction 的资源模型不同。存算分离后,持久数据放对象存储,Query/Data/Streaming 节点可以针对不同瓶颈独立扩缩容;计算节点故障后也能从共享存储恢复状态。代价是网络、对象存储依赖和更复杂的调度。
15.4 HNSW 和 IVF 怎么选
HNSW 用图遍历,通常适合低 Top-K、高 Recall、内存充足的在线查询;IVF 先聚类,只搜索部分桶,内存和大 Top-K 场景更灵活。HNSW 重点调 M、efConstruction、ef;IVF 重点调 nlist、nprobe。最终用真实数据比较 Recall-P99 和成本。
15.5 为什么 ANN 会丢 Recall
ANN 为了避免遍历全部向量,只搜索图的一部分、若干聚类桶或压缩后的距离,因此可能错过真正近邻。扩大 ef/nprobe、减弱量化、增加候选后精排可以提高 Recall,但会增加延迟和资源消耗。
15.6 COSINE、IP、L2 的区别
COSINE 看方向,IP 同时受方向和模长影响,L2 看欧氏距离。单位归一化后,COSINE 与 IP 排序等价,L2 也与它们存在单调关系。工程上应遵循 Embedding 模型训练时使用的度量,并保证建索引和查询一致。
15.7 Milvus 中为什么有 Growing 和 Sealed Segment
Growing Segment 负责接收实时写入并保证新数据可查询;Sealed Segment 不再写入,可以构建更高效的 ANN 索引。这样同时兼顾实时性和历史数据查询性能,之后通过 Compaction 管理碎片。
15.8 Flush 越频繁数据越安全吗
不是。写入先进入 WAL,Flush 是推进持久化,不应每条调用。过度 Flush 会产生大量小 Segment,增加索引构建、加载和 Compaction 开销。应根据批量写入、恢复目标和官方机制设计,而不是把 Flush 当成事务 Commit。
15.9 Strong Consistency 是否一定最好
不是。Strong 让搜索等待最新写入可见,适合写后立即读,但可能增加延迟。大多数知识库允许秒级更新延迟,可以使用 Bounded Staleness;同一用户上传后立即问答可考虑 Session 或 Strong。
15.10 为什么不能召回后再做权限过滤
后过滤会截断候选。如果无权限数据占据 Top-K,过滤后结果数量不足,而本来相关且有权限的文档没有机会进入候选。权限条件应进入 Dense、Sparse 和缓存等所有召回路径。
15.11 为什么还要 BM25
Dense 擅长同义表达,但可能模糊错误码、数字、型号和罕见实体;BM25 擅长精确词面匹配。两者并行召回后用 RRF 或加权融合,通常比单路更稳健。
15.12 Milvus 内部 Rerank 与 Cross-Encoder 有什么区别
Milvus 的 RRF/Weighted Ranker 主要融合多路召回结果,通常基于排名或分数;Cross-Encoder 会联合编码 Query 和 Document,做更昂贵但更精细的相关性判断。前者解决融合,后者解决候选内部精排。
15.13 如何保证文档更新不重复
使用稳定主键或内容哈希,Upsert 时记录 document_version、chunk_version 和 embedding_version。推荐先写新版本、验证后切换 active 标记,再异步删除旧版本,避免先删后写造成空窗。还要监控旧版本残留。
15.14 Milvus 为什么比 pgvector 更适合大规模向量
不是简单说“更快”,而是 Milvus 把查询、写入、索引和共享存储拆开,可以独立扩展,并提供更丰富的专用索引、多向量、Sparse/BM25、磁盘和 GPU 路径。pgvector 的优势是 SQL、事务、Join 和复用 PostgreSQL;中小规模时它可能更合适。
15.15 如何评估一个 RAG 检索系统
先标注 Query-Document 相关性,离线测 Recall@K、MRR、NDCG,再测 P95/P99、QPS 和资源。分开评估 Dense、BM25、融合、Rerank,最后测答案正确率、引用正确率、无答案拒答和端到端延迟。
15.16 数据量从 100 万涨到 1 亿怎么办
先重新做容量估算和基准测试:向量原始内存、索引开销、副本、过滤、Top-K、并发。根据内存和延迟选择 HNSW 压缩、IVF_PQ/SQ、DiskANN 或 mmap;再考虑增加 Query/Data 节点、分片、冷热分层和批量导入。不能只增加机器而不检查 Chunk 冗余和索引参数。
15.17 为什么查询慢,怎么定位
分层拆延迟:Query Embedding、网络、Milvus Search、过滤、结果回传、Reranker、LLM。Milvus 内部再看热点 Collection、Load 状态、Segment 数、索引参数、过滤索引、节点 CPU/内存/磁盘、对象存储和多分片归并。先定位阶段,再调参数。
15.18 如何选择向量数据库
我会先定义数据规模、增长率、QPS、P99、Recall、过滤、多租户、更新实时性、合规和预算,再用同一数据、Embedding、Top-K 和硬件压测候选系统。已有 PostgreSQL/ES 时优先评估复用成本;需要大规模专用向量扩缩容时再考虑 Milvus 等独立系统。
16. 容易说错的点
- “RAG 必须用向量数据库”——错,小规模可以不用;
- “向量数据库会生成 Embedding”——通常 Embedding 由模型生成,数据库负责存储检索;Milvus 也提供部分 Function/集成,但两层概念不能混淆;
- “COSINE 距离越小越相似”——在 Milvus 的 COSINE similarity 语义下通常越大越相似;
- “HNSW 一定比 IVF 好”——取决于内存、Top-K、过滤、Recall 和数据规模;
- “索引建好后 Search 就一定准确”——ANN、Embedding、Chunking 都会影响 Recall;
- “Strong Consistency 会提高语义相关性”——它只影响数据新鲜度;
- “Flush 等于关系数据库 Commit”——两者语义不同;
- “Partition 越多越快”——过多 Partition 会增加管理和调度成本;
- “先召回再做租户过滤即可”——会泄漏风险并造成候选截断;
- “Milvus 一定比 pgvector/ES 快”——没有统一硬件和真实负载的基准,不能这样下结论;
- “RRF 就是模型 Reranker”——RRF 是基于排名的融合算法,不是 Cross-Encoder;
- “只监控 QPS/P99 就够了”——检索系统必须同时监控 Recall 和业务答案质量。
17. 项目介绍模板
面试时可以按“背景—设计—难点—指标—取舍”描述:
我们为内部技术文档构建 RAG。文档解析后按标题和段落切块,保留 document_id、page_no、tenant_id、ACL 和版本信息,使用统一 Embedding 模型生成 Dense Vector 并批量写入 Milvus。在线查询同时执行 Dense 和 BM25 召回,权限条件在两路召回阶段下推,之后用 RRF 合并,再用 Cross-Encoder 将 Top-50 精排到 Top-5。索引选择不是拍脑袋,我们用 FLAT 结果作为 Ground Truth,对 HNSW 的 ef 和 IVF 的 nprobe 做 Recall-P99 曲线,最后在目标 Recall 下选择资源成本更低的方案。文档更新采用新版本先写、验证后切换 active 标记、旧版本延迟删除,避免知识库空窗。监控同时覆盖 P99、写入可见延迟、零结果率和 Recall 回归。
如果被追问“为什么选择 Milvus”:
当时向量数据需要独立扩缩容,而且后续要支持更大规模、多租户过滤、Dense+Sparse 和磁盘索引,所以没有把向量工作负载继续放在 OLTP PostgreSQL 中。Milvus 的存算分离和索引选择更适合这个方向。代价是多维护一个分布式系统和数据同步链路,所以小规模阶段我不会直接上 Distributed,而会从 Lite/Standalone 验证,再按指标迁移。
18. 速记总结
1 | RAG:给 LLM 找证据,不是把所有知识塞进 Prompt |
参考资料
以下资料均优先使用各项目官方文档;本文以 Milvus 2.6.x 稳定 API 为主,具体参数应以部署版本文档为准。
- Milvus Overview
- Milvus Architecture Overview
- Milvus Deployment Options
- Milvus Lite
- Milvus Index Explained
- Milvus Metric Types
- Milvus Consistency
- Milvus Full Text Search
- Milvus Multi-Vector Hybrid Search
- Milvus Multi-tenancy
- pgvector
- Faiss
- Qdrant Distributed Deployment
- Qdrant Hybrid Queries
- Weaviate Hybrid Search
- Weaviate Replication Architecture
- Chroma Architecture
- Pinecone Indexing Overview
- Elasticsearch kNN Search
