Milvus 从入门到入土:把向量数据库、底层原理和工程实战一次讲透

更新时间:2026-03-18
适用范围:以 Milvus 官方文档 v2.6.x 为主,辅以部分 v2.2/v2.3 文档解释历史架构名词差异。
阅读目标:看完以后,你应该能回答下面这些问题:

  • Milvus 到底是什么,和 MySQL / Elasticsearch / Faiss 的关系是什么?
  • Collection、Partition、Shard、Segment、Growing、Sealed、Flush、Compaction 分别是什么?
  • 为什么 load() 之后才能查?为什么频繁 flush() 反而可能变慢?
  • 写入一条向量后,Milvus 在后台到底做了什么?
  • 查询一条向量时,Proxy、QueryNode、DataNode、WAL、MinIO、etcd 分别在干嘛?
  • upsert 为什么是按主键工作的?为什么 auto_id=True 时它不一定适合业务去重?
  • Milvus 为什么适合做海量向量检索?为什么它能扩展成集群?
  • 面试时怎样把 Milvus 讲得像一个“真正理解过”的系统,而不是 API 使用者?

如果你是第一次接触向量数据库,这篇文章会尽量用“人话 + 工程视角”把 Milvus 讲清楚;如果你已经在做 RAG、知识库检索、图片检索、推荐召回,这篇文章则会帮你把很多“会用但说不透”的点真正串起来。

你可以把这篇文章理解成两条线同时展开:

  • 概念线:Collection、Segment、Flush、Load、Index、Query 到底是什么
  • 工程线:怎么建模、怎么导入、怎么选索引、怎么理解写入与查询链路、怎么避坑

1. Milvus 是什么

一句话版:

Milvus 是一个开源、云原生、面向海量向量数据的数据库,核心能力是“存向量 + 建索引 + 近邻检索 + 标量过滤 + 分布式扩展”。

你可以把它理解成:

  • 不是“大模型”本身,它不负责训练模型。
  • 不是“embedding 模型”本身,它不负责把文本/图片变成向量,虽然现在也支持一些 embedding function。
  • 它更像是“向量数据的数据库 + 检索执行引擎”。

如果类比:

  • MySQL 擅长结构化数据、事务、精确查询。
  • Elasticsearch 擅长倒排、全文检索、日志检索。
  • Faiss 擅长单机向量索引和近邻搜索。
  • Milvus 则是在 Faiss/HNSW/DiskANN 这类检索能力之上,补齐了数据库层、分布式层、持久化层、元数据管理层、集群扩展层。

Milvus 的典型用途:

  • RAG 知识库检索
  • 图搜图、文搜图、图文混搜
  • 推荐召回
  • 专利、论文、多媒体相似检索
  • 稠密向量 + 稀疏向量 + 标量过滤的混合检索

官方资料里也明确强调了 Milvus 面向从本地原型到大规模分布式场景的多种部署模式,并且是“计算与存储分离”的架构。


2. Milvus 解决的根问题是什么

传统数据库擅长的是“精确匹配”:

  • id = 123
  • name like 'apple%'
  • price > 10

但向量检索的核心问题是:

  • “这个文本和哪些文本语义最像?”
  • “这张图片和图库中哪些图片最相似?”
  • “这个专利 claims 向量最接近哪些历史专利?”

这时你存的不是一句话本身,而是一个高维向量,比如:

  • 384 维
  • 768 维
  • 1024 维

然后你的查询不再是 SQL 精确比较,而是:

  • 计算余弦相似度 COSINE
  • 计算欧氏距离 L2
  • 计算内积 IP

如果你把 1 亿条向量都暴力全扫,肯定慢。所以 Milvus 的核心价值就是:

  1. 把向量和元数据组织成数据库里的结构
  2. 给向量建适合 ANN 的索引
  3. 用分布式方式存、管、查
  4. 在召回速度、召回率、成本之间做工程平衡

3. Milvus 的基础对象

3.1 Collection

Collection 可以理解为“表”。

官方文档的定义是:Collection 是一个二维表,固定列、可变行;列叫 field,行叫 entity。

例子:

  • Collection_Main
  • Collection_Claims
  • Collection_Images

你项目里也就是这么用的:

3.2 Field

Field 就是列。

可以是:

  • 标量字段:VARCHARINT64BOOLJSONARRAY
  • 向量字段:FLOAT_VECTORSPARSE_FLOAT_VECTOR

例如你的 Collection_Claims

  • application_number
  • claim_number
  • claim_text
  • claim_vector_dense
  • claim_vector_sparse

3.3 Entity

Entity 就是一行数据。

比如一条权利要求:

  • application_number = US123...
  • claim_number = 1
  • claim_text = A method for ...
  • claim_vector_dense = 768 维向量

3.4 Primary Field

Primary Field 就是主键。

Milvus 的很多操作都依赖主键:

  • insert
  • delete
  • query
  • upsert

主键可以是:

  • INT64
  • VARCHAR

3.5 AutoID

如果启用 auto_id=True,Milvus 自动给你生成主键。

优点:

  • 导入简单
  • 不用自己维护唯一 ID

缺点:

  • 你的业务主键不再等于 Milvus 主键
  • upsertdelete(ids=...)、主键定位这类能力会没那么顺手

你的项目当前就是这种设计:主键是自增 id,业务字段 application_number 不是主键。

参考:


4. Partition、Shard、Segment 到底分别是什么

这三个词最容易混。

4.1 Partition

Partition 是 Collection 的子集。

它更偏“逻辑分组”:

  • 按租户分
  • 按年份分
  • 按业务域分

好处:

  • 可以缩小搜索范围
  • 方便管理

坏处:

  • 不是越多越好
  • 分太碎会有管理和查询负担

一句话记忆:

Partition 是“你主动设计的逻辑分区”。

4.2 Shard

Shard 是 Collection 的水平切片,对应数据输入通道。

官方文档里说得很直接:Shard 是 Collection 的水平切片,每个 shard 对应一个 data input channel。

它主要服务于:

  • 写入吞吐扩展
  • 并发处理
  • 数据路由

一句话记忆:

Shard 是“系统为了吞吐做的并行写入切分”。

4.3 Segment

Segment 是 Milvus 真正存放数据和建立索引的基本物理单元。

这是最重要的对象之一。

你可以把它理解成:

  • Collection 是逻辑大表
  • Segment 是表下面一块一块的物理数据块

Milvus 不会把一个 collection 直接当成一个整体文件去处理,而是拆成很多 segment。

原因很简单:

  • 便于并行写入
  • 便于并行检索
  • 便于单块建索引
  • 便于 compaction
  • 便于分布到不同节点

一句话记忆:

Segment 是“Milvus 内部真正拿来存、查、建索引、搬运的数据块”。


5. Growing Segment 和 Sealed Segment

这是你这次问题的核心。

5.1 Growing Segment

Growing Segment 是“还在长”的 segment。

官方文档在数据处理章节里说:

  • Growing segment:还没有持久化到对象存储的数据

特征:

  • 还能继续写入
  • 通常没有最终稳定索引
  • 更偏实时写入态

5.2 Sealed Segment

Sealed Segment 是“封口了”的 segment。

官方文档里说:

  • Sealed segment:数据已经持久化到对象存储,且不可变

特征:

  • 不再接收新写入
  • 数据不可变
  • 更适合建索引和查询

5.3 为什么要区分 Growing 和 Sealed

因为“写入中”与“已稳定可检索”是两种完全不同的工程状态。

如果一个数据块还在持续写入:

  • 数据量还在变
  • 向量分布还在变
  • 立即反复重建索引成本很高

所以 Milvus 的思路是:

  1. 先把新数据写入 growing segment
  2. 累积到一定条件后 seal
  3. sealed 后再做更稳定的索引构建和 handoff

一句话记忆:

  • growing = 写入态
  • sealed = 稳定态

6. Seal 是什么,Flush 又是什么

6.1 Seal

Seal 就是“封口”。

一个 segment 被 seal 之后:

  • 不再接受新写入
  • 变成不可变
  • 后续适合持久化、建索引、被查询节点加载

6.2 Flush

Flush 不是“建索引”本身。

Flush 更准确的含义是:

  • 促使 growing segment 结束写入态
  • 进入 seal / 持久化 / 后续可索引的流程

官方 API 文档对 flush() 的表述非常关键:

  • flush() 会 seal collection 里的 segment
  • 之后的新插入会生成新的 segment
  • 如果你持续调用 flush(),会产生很多小的 sealed segment,搜索性能会逐渐下降

这就是为什么:

  • flush() 不是越勤快越好
  • 它更像“催收口”
  • 频繁 flush() 会导致 segment 过碎

6.3 自动 flush 与手动 flush

Milvus 本身就会在合适的时候自动把 growing segment seal 掉。

所以通常:

  • 大多数导入流程不需要每插一批就手动 flush()
  • 更合理的是按较大批次 flush,甚至只在最后 flush

6.4 你可以怎么理解

最粗暴的类比:

  • Growing segment 像正在写的 Word 文档
  • Sealed segment 像点了“定稿锁定”的 PDF
  • Flush 像“把当前这份先定稿,不要再往里写了”

7. 为什么频繁 flush 会拖慢性能

因为会制造很多“小而碎”的 sealed segments。

官方文档已经明确说了:持续调用 flush() 会产生很多小 segment,并逐步降低搜索性能。

为什么?

因为一次查询不是只查“一个 collection 文件”,而是要查“很多个 segment”:

  • 每个 segment 都要参与路由、调度、过滤、合并
  • segment 越多,调度成本越高
  • 小 segment 越多,索引和 metadata 的碎片越多
  • 后续 compaction 压力也越大

所以一个常见的性能思路是:

  • 不要频繁手动 flush
  • 尽量让 segment 长到合适大小
  • 再 seal、建索引、handoff

8. Load 和 Release 到底是什么

8.1 Load

load() 的本质是把可查询所需的数据和索引加载到内存中。

官方文档明确写了:

  • 搜索和 query 之前,load 是前提
  • load 时,Milvus 会把 index files 和 raw data 加载到内存
  • 如果 collection 已经 load,之后新插入的数据会自动被索引并加载

这句话很重要,因为它回答了很多误解:

  • load() 不是“重建索引”
  • load() 也不是“把所有历史数据重新导一遍”
  • load() 更像“让这张表进入可查询状态”

8.2 Release

release() 就是把已经 load 的 collection 从内存中卸下来。

适用于:

  • 暂时不查这张表
  • 节省查询节点内存
  • 让资源给别的 collection

8.3 插入后为什么还能查到

因为如果 collection 已经 load,Milvus 会让后续插入的数据自动被索引并加载。

所以你的脚本里一开始 load(),然后边导边查重,本身不是错。


9. Index 是什么

Index 是建在数据之上的附加结构。

官方文档的定义非常经典:

  • 索引是 built on top of data 的额外结构
  • 它能加速搜索
  • 但会带来额外的预处理时间、空间开销、内存开销
  • ANN 索引通常还会用少量召回率换速度

这和传统数据库索引类似,但又不完全一样。

传统数据库索引:

  • B+ 树
  • Hash
  • 倒排

向量数据库索引:

  • FLAT
  • IVF_FLAT
  • IVF_SQ8
  • IVF_PQ
  • HNSW
  • DISKANN
  • SPARSE_INVERTED_INDEX

一句话记忆:

索引不是数据本身,而是“为了更快找到近邻而建立的辅助结构”。


10. 常见索引怎么选

10.1 FLAT

暴力检索,不做近似。

特点:

  • 最准
  • 最慢
  • 最吃算力

适合:

  • 小数据集
  • 基准测试
  • 校验召回率

10.2 IVF_FLAT / IVF_SQ8 / IVF_PQ

IVF 系列的核心思想:

  • 先把向量粗分桶
  • 查询时先找最相关的桶
  • 只在部分桶里继续搜

区别:

  • IVF_FLAT:桶内存原始向量
  • IVF_SQ8:桶内向量做标量量化,省内存
  • IVF_PQ:进一步压缩,省得更多,但误差更大

你项目现在大量使用的是 IVF_SQ8,这是一个很典型的工程折中:

  • 比 FLAT 省内存、省成本
  • 比 PQ 更稳一些

10.3 HNSW

图索引,通常高召回、低延迟,但内存占用高。

适合:

  • 追求高召回、高性能在线查询
  • 内存预算充足

10.4 DISKANN

面向大规模磁盘检索。

适合:

  • 数据特别大
  • 希望把更多压力放到磁盘而非内存

10.5 SPARSE_INVERTED_INDEX

用于稀疏向量,尤其是 BM25 / 全文检索场景。

你项目里 claim_vector_sparse 就是这种路线。


11. Search、Query、Get 的区别

Search 是“按向量相似度找近邻”。

输入通常是:

  • 一个 query vector
  • 指定向量字段
  • 指定 metric
  • topK

输出通常是:

  • 最相似的若干条实体

11.2 Query

Query 是“按标量条件过滤取数据”。

例如:

  • application_number == "US..."
  • claim_number in [1,2,3]

它不强调向量近邻,而是偏数据库式筛选。

11.3 Get

Get 是“按主键取数据”。

如果你知道主键,直接拿。

一句话记忆:

  • search = 向量近邻
  • query = 条件过滤
  • get = 主键直取

12. 写入一条数据时,Milvus 后台发生了什么

这是最值得掌握的主线。

以 v2.6 的官方架构描述为主,可以理解为:

  1. 客户端通过 SDK 或 REST 把写入请求发给 Proxy
  2. Proxy 做请求校验
  3. Proxy 按 shard 路由把数据送到对应 Streaming Node
  4. Streaming Node 为数据分配时间戳 TSO
  5. Streaming Node 先把操作写入 WAL
  6. 数据进入 growing segment
  7. 在 segment 满足条件或 flush 后,growing 变 sealed
  8. Data Node 对 sealed segment 做离线处理,比如 compaction、建索引
  9. 索引文件和日志快照写入对象存储
  10. Query Node 加载 sealed segment 及其索引,参与稳定查询
  11. Coordinator 做 handoff,把实时态转为历史态

你要把这条链子记成一句话:

写入链路 = Proxy 接请求 -> StreamingNode 记 WAL -> Growing Segment 吃数据 -> Flush/Seal -> DataNode 建索引 -> QueryNode 加载查询


13. 查询一条向量时,Milvus 后台发生了什么

同样按官方主线理解:

  1. 客户端发起 search / query
  2. 请求先到 Proxy
  3. Proxy 根据路由信息找到相关 shard / 节点
  4. 对 growing 数据,由负责该 shard 的节点查实时数据
  5. 对 sealed 数据,由 Query Node 查历史数据和索引
  6. 各节点返回局部 topK
  7. Proxy 做归并和后处理
  8. 返回最终结果

这说明一件事:

一次查询不只是“查一个索引文件”,而是一个分布式归并过程。

所以面试里你可以说:

“Milvus 的查询是多阶段的,既要覆盖 growing segment 的实时数据,也要覆盖 sealed segment 的历史数据,最后由 Proxy 或中间层做归并。”

这句话很像真正理解过系统的人说的。


14. Compaction 是什么

Compaction 可以理解为“整理碎片、合并数据块、优化存储布局”。

它不是简单的“压缩文件”,而是数据重写和重组织。

Milvus 做 compaction 的动机:

  • segment 太碎
  • 删除标记需要清理
  • 查询要减少无关 segment
  • 让后续检索更高效

14.1 普通 compaction

常见用途:

  • 合并小 segment
  • 清理删除痕迹
  • 优化存储布局

14.2 Clustering Compaction

这是 Milvus 较新的高级优化。

官方文档里说得很清楚:

  • Milvus 会把实体存到多个 segment
  • 如果 segment 是随机分布的,查询时往往要扫很多 segment
  • clustering compaction 可以按某个标量字段重排 segment
  • 这样带过滤条件的查询就能 prune 掉很多无关 segment

最典型场景:

  • 多租户
  • 按 tenant_id / category / time_range 检索

一句话记忆:

Compaction 是“把已经写进去的物理数据重新整理得更适合查”。


15. Delete 为什么不是立刻“物理删除”

从 Milvus 的设计看,删除更像:

  • 先记录删除操作
  • 读取时通过 bitset / tombstone 之类的机制把它屏蔽
  • 后续 compaction 再做更彻底的物理整理

这和很多 LSM / 日志型系统的思路是类似的。

你要理解:

  • 分布式存储里,“逻辑删除先行,物理回收滞后”很常见
  • 这样可以兼顾写入吞吐、时间旅行、恢复能力

16. Time Travel 和 retentionDuration

Milvus 支持 Time Travel,也就是“按某个时间点看数据视图”。

官方文档提到:

  • Milvus 会给 DDL/DML 事件打时间戳
  • 默认允许的 Time Travel 保留时长是 432000 秒,也就是 120 小时
  • common.retentionDuration 控制删除数据可用于 Time Travel 的保留时长

这件事意味着:

  • 删除过的数据不一定马上永远不可见
  • 在一定保留窗口内,系统还要维护历史可见性
  • 因此 compaction 也不能胡来,要考虑 retention window

17. 一致性等级:Strong、Bounded、Session、Eventually

Milvus 是分布式系统,所以“刚写进去的数据,立刻查,必须看到吗?”这个问题就变成一致性问题。

官方文档给了四种一致性等级:

  • Strong
  • Bounded
  • Session
  • Eventually

默认一般是 Bounded

17.1 Strong

尽量保证读到最新写入。

代价:

  • 等待更多同步
  • 延迟可能更高

17.2 Eventually

更偏最终一致。

好处:

  • 更快

代价:

  • 刚写入的数据可能暂时查不到

17.3 Session

更适合单会话里“我自己写的数据,我最好能看见”。

17.4 Bounded

在新鲜度和延迟之间折中。

这也是生产里常见的默认选择。

面试里的回答方式:

“Milvus 不是单机库,而是分布式向量数据库,所以它把一致性做成了可配置项。你可以在读到最新数据和查询延迟之间做权衡。”


18. Upsert 到底是什么,为什么很多人会误用

官方定义很明确:

  • upsert 是“如果主键存在就更新,不存在就插入”
  • 它是按 primary key 判断的

这是第一重点。

不是按:

  • application_number
  • file_name
  • 业务唯一键

而是按 Milvus 主键。

18.1 如果主键是 AutoID 会怎样

官方文档还明确写了:

  • 如果 collection 的主键启用了 autoid
  • upsert 时 Milvus 会为请求里的数据生成新的主键再插入

这就意味着:

  • auto_id=True 时,upsert 不一定能达到你想象中的“按业务键覆盖更新”
  • 如果你的业务唯一标识不是主键,upsert 不能天然替你去重

这正是你项目里之前那个问题的根本原因。

18.2 什么时候适合用 upsert

适合:

  • 你的业务 ID 就是 Milvus 主键
  • 你明确需要“有则更新,无则插入”

不适合直接依赖:

  • 主键是 AutoID
  • 真正的业务唯一键只是普通字段
  • 你还想靠它省掉云端 embedding 成本

因为 embedding 成本发生在“你决定调用模型”的那一刻,而不是 Milvus upsert 那一刻。


19. 你这类导入任务里,真正该优先理解的顺序

很多人一接触 Milvus,脑子里先冒出来的是:

  • 要不要 upsert
  • 要不要 flush
  • 要不要 load

但更正确的思考顺序应该是:

  1. 业务唯一键是什么
  2. Milvus 主键是什么
  3. 去重发生在 embedding 之前还是之后
  4. collection 是否已经 load
  5. flush 是否过于频繁
  6. segment 会不会被打碎
  7. 索引是在后台增量补建还是被你手动反复重建

这才是工程上真正影响成本和性能的顺序。


20. gRPC 是什么,Milvus 为什么用它

20.1 先讲 RPC

RPC = Remote Procedure Call,远程过程调用。

意思就是:

  • 你的程序像调用本地函数一样
  • 去调用远程机器上的服务

20.2 gRPC 是什么

gRPC 官方的定义是:

  • 一个高性能、开源、通用的 RPC 框架
  • 基于 HTTP/2
  • 常配合 Protocol Buffers
  • 支持负载均衡、追踪、健康检查、认证等能力

20.3 Milvus 为什么适合用 gRPC

因为 Milvus 是典型的分布式系统:

  • SDK 要和 Proxy 通信
  • Proxy 要和内部组件通信
  • 集群里很多节点之间要做高频低延迟调用

gRPC 很适合这种场景:

  • 二进制协议,效率高
  • 跨语言
  • 强类型接口定义
  • 适合高并发服务间调用

20.4 Milvus 的端口

官方连接文档说明:

  • 19530:gRPC 和 RESTful API
  • 9091:metrics、pprof、health probe 等管理端口

所以你平时连 Milvus 看到的 19530,本质上是服务入口端口。


21. Milvus 为什么是“计算与存储分离”

这是 Milvus 非常核心的架构思想。

21.1 传统一体化数据库的特点

很多传统数据库里:

  • 数据存在哪台机器
  • 查询也在那台机器
  • 索引也在那台机器

耦合比较紧。

21.2 Milvus 的特点

Milvus 更像:

  • 元数据放 etcd
  • 大文件放 MinIO/S3
  • WAL 放消息队列或 Woodpecker
  • 计算节点只是“无状态执行者”

这带来的好处:

  • 查询节点可以单独扩
  • 数据节点可以单独扩
  • 索引节点可以单独扩
  • 节点挂了更容易恢复
  • 更适合上 Kubernetes

一句话记忆:

Milvus 的核心不是“单机超强”,而是“分布式、解耦、可横向扩展”。


22. Milvus 集群里都有哪些组件

这里要分“新文档主线”和“老版本常见名词”两套说法。

22.1 v2.6 主线理解

官方 v2.6 架构概览里,Milvus 被拆成四层:

  1. Access Layer
  2. Coordinator
  3. Worker Nodes
  4. Storage

其中:

  • Access Layer:Proxy
  • Coordinator:协调器,负责拓扑、调度、一致性
  • Worker Nodes:Streaming Node、Query Node、Data Node
  • Storage:etcd、Object Storage、WAL

22.2 老版本 / 传统面试常见名词

在很多资料和旧版本部署里,你会看到这组名字:

  • proxy
  • root coord
  • data coord
  • query coord
  • index coord
  • data node
  • query node
  • index node

v2.3 文档甚至明确写了:

  • 最初 Milvus 2.0 集群包含这 8 个组件
  • 后来引入 mix coord
  • mix coord 包含所有 coordinator 组件

22.3 这两套说法冲突吗

不冲突。

你可以把它理解成:

  • “控制平面 + 数据平面 + 共享存储”的大思想没变
  • 只是组件划分方式、命名方式、打包方式在演进

面试时很推荐你这样回答:

“Milvus 的核心架构思想没有变,就是 Proxy 作为接入层,Coordinator 负责控制平面,Query/Data/Index 负责执行平面,底层依赖 etcd、对象存储和 WAL。不同版本会把这些能力拆成不同组件,比如历史上常见的 rootCoord/dataCoord/queryCoord/indexCoord,后来也有 mixCoord、StreamingNode 这样的演进。”

这就很像真正懂系统演进的人。


23. 各组件分别干什么

23.1 Proxy

前门。

职责:

  • 接 SDK / REST 请求
  • 校验请求
  • 路由请求
  • 聚合结果

它本身通常是无状态的,适合多副本扩容。

23.2 RootCoord

偏“元操作”和时间戳管理”。

负责:

  • DDL / DCL
  • collection / partition / index 的创建删除
  • TSO

23.3 DataCoord

偏“数据后台运维调度”。

负责:

  • segment 管理
  • flush / compact 等后台数据操作
  • data node 拓扑管理

23.4 QueryCoord

偏“查询资源调度”。

负责:

  • query node 负载均衡
  • handoff
  • 查询侧拓扑管理

23.5 IndexCoord / IndexNode

负责索引构建任务管理和执行。

23.6 StreamingNode / DataNode / QueryNode

可以按一句话记忆:

  • Streaming / Data 写入链路
  • Query 查询链路
  • Index 建索引链路

24. Milvus 依赖的外部组件分别是什么

官方文档明确说 Milvus 依赖:

  • MinIO
  • Kafka
  • Pulsar
  • etcd

新版还有 Woodpecker 作为 WAL 方案。

24.1 etcd

etcd 是元数据存储。

它负责:

  • collection schema
  • 节点状态
  • checkpoint
  • 服务注册
  • 健康检查

为什么选 etcd:

  • 高可用
  • 强一致
  • 适合存元数据

24.2 MinIO / S3

对象存储。

负责:

  • 日志快照
  • 索引文件
  • 原始数据持久化

特点:

  • 容量大
  • 成本低
  • 延迟比内存高

24.3 Pulsar / Kafka / Woodpecker

这是 WAL / 消息通道层。

职责:

  • 写前日志
  • 顺序写入
  • 恢复重放
  • 流式数据传递

官方新文档里,Woodpecker 是 Milvus 2.6 的可选 WAL,强调云原生、低运维、面向对象存储。

24.4 RocksMQ / RocksDB

这是旧版 standalone 常见的轻量消息层。

你看到老文档里有它,不要惊讶。


25. Replica 是什么

Replica 不是“简单复制一份数据文件”这么浅。

官方 in-memory replica 文档说:

  • 相同 segment 可以被加载到多个 query nodes
  • 这样既能提高吞吐,也能提高可用性

你可以这样理解:

  • 一份 segment,多个查询节点都能服务
  • 某个查询节点忙或挂了,流量可以切到另一个副本

作用:

  • 提升 QPS
  • 降低故障影响
  • 加快 failover

26. 为什么 Milvus 适合 Kubernetes 和集群扩展

因为它大量组件是无状态或弱状态执行者,状态被外置:

  • 元数据去 etcd
  • 数据和索引去对象存储
  • 流式日志去 WAL

这样带来三个巨大好处:

26.1 读写建索引可以分开扩

  • 读多:加 QueryNode
  • 写多:加 DataNode / StreamingNode
  • 建索引多:加 IndexNode

26.2 故障恢复更简单

节点不是唯一真相来源。

挂了可以:

  • 重拉 pod
  • 从 etcd / 对象存储 / WAL 恢复

26.3 更适合弹性伸缩

这就是云原生数据库最想要的能力。


27. Milvus 的三种部署模式

官方概述页明确提到三种部署模式:

27.1 Milvus Lite

适合:

  • 本地原型
  • Jupyter
  • 小规模实验

27.2 Milvus Standalone

适合:

  • 单机部署
  • 开发测试
  • 中小规模业务

特点:

  • 组件打包在一个实例里
  • 部署简单

27.3 Milvus Distributed

适合:

  • Kubernetes
  • 大规模生产
  • 高可用与弹性扩容

28. 用一个真实导入案例,把前面的概念真正串起来

28.1 load() 是为了可查询,不是为了重建索引

假设你在做一个“专利向量导入任务”或者“知识库批量入库任务”,导入脚本通常会在建库、建索引、插入后调用 load()

这里特别容易误解成:

  • 调了 load(),索引才真正建立
  • 不调 load(),数据就还没准备好

其实更准确的理解是:

  • 索引构建和持久化是后台流程
  • load() 的意义是把数据和索引加载到查询节点,进入可查询状态

所以它更像“把仓库里的货搬到门店上架”,而不是“现场重新造货”。

28.2 频繁 flush() 可能把 segment 打碎

所以它不是“越勤越安全”,而是“越勤越可能制造碎片”。

28.3 你现在的 schema 主键是 AutoID

因此:

  • upsert 无法天然按 application_number 去重
  • 真正业务唯一键和 Milvus 主键不是一回事

28.4 真正省钱的关键是 embedding 前去重

不是先求向量,再指望 Milvus 帮你去重。

Milvus 解决的是“存和查”,不是替你把云端 embedding 账单抹掉。


29. 面试时可以怎么讲 Segment / Seal / Flush

你可以直接背这段:

“Milvus 内部不是按整个 collection 作为一个物理整体处理,而是拆成多个 segment。新写入先进入 growing segment,它处于可写状态;当 segment 达到一定条件或者被 flush 后,就会从 growing 转成 sealed。sealed 之后数据不可变,更适合持久化、建索引和被查询节点加载。频繁手动 flush 会制造很多小的 sealed segments,导致查询和后台 compaction 的开销上升,所以生产上一般不建议每小批次都 flush。”

这段非常像真正理解过的人说的话。


30. 面试时可以怎么讲 Milvus 架构

可以直接背这段:

“Milvus 本质上是一个计算与存储分离的分布式向量数据库。前面是 Proxy 作为接入层,负责请求校验、路由和结果归并;中间是 Coordinator 负责控制平面,比如元数据、拓扑、时间戳、调度;执行层是 QueryNode、DataNode、IndexNode 或新版本里的 StreamingNode、QueryNode、DataNode;底层依赖 etcd 存元数据、MinIO/S3 存对象和索引文件、WAL 系统保证流式写入和恢复。这样设计的好处是读、写、建索引可以分别横向扩展。”


31. 面试时可以怎么讲 gRPC

可以直接背这段:

“Milvus 是分布式系统,组件之间和客户端到服务端之间需要高性能远程调用,所以它使用 gRPC 这类基于 HTTP/2 的 RPC 框架。gRPC 的好处是二进制协议、强类型接口定义、跨语言、低延迟,并且很适合服务间通信。Milvus 默认服务端口 19530 就是 gRPC/REST 的接入端口,9091 则偏管理和监控。”


32. 最容易犯的理解错误

错误 1:Milvus 就是 Faiss

不对。

Faiss 更像检索库;Milvus 是数据库系统,底层可能用到 Faiss 等搜索库。

错误 2:load() = 重建索引

不对。

load() 是把数据和索引装进内存进入可查询态。

错误 3:flush() = 建索引

不对。

flush() 更像促使 growing segment seal,并进入后续持久化/建索引流程。

错误 4:upsert 可以按任意字段去重

不对。

upsert 是按主键,不是按你心里默认的业务键。

错误 5:向量数据库不需要考虑一致性

不对。

Milvus 是分布式系统,也有 Strong / Bounded / Eventually 这类一致性权衡。


33. 如果你只记 10 句话,记这 10 句

  1. Milvus 是向量数据库,不是 embedding 模型。
  2. Collection 像表,Field 像列,Entity 像行。
  3. Partition 是逻辑分组,Shard 是并行写入切片,Segment 是物理数据块。
  4. 新数据先进入 growing segment。
  5. growing 变成 sealed,意味着这块数据不再写、适合查、适合建索引。
  6. flush() 会促使 segment seal,频繁 flush 会制造很多小 segment。
  7. load() 是把数据和索引加载到内存供查询,不是重建索引。
  8. upsert 是按主键工作的,不是按普通业务字段工作的。
  9. Milvus 的核心架构是 Proxy + Coordinator + Worker Nodes + Storage。
  10. etcd 管元数据,MinIO/S3 管对象和索引,WAL 保证写入顺序与恢复。

34. 如果你正在做导入或检索项目,最后给你 4 条真正有用的建议

无论你做的是专利检索、RAG 知识库、图片相似搜索,最重要的都不是“看见 Milvus 就立刻想着上什么索引”,而是先问下面几个更根本的问题:

  • 你的业务唯一键到底是什么?
  • 这个唯一键是不是 Milvus 主键?
  • 去重发生在云端 embedding 之前还是之后?
  • 你是不是在频繁 flush,把 segment 打碎了?

大多数项目里,优先级通常是:

  1. embedding 前做去重
  2. 避免多线程重复处理同一个业务 ID
  3. 不要频繁 flush
  4. 如果未来真要 upsert,就把业务唯一键设计成 Milvus 主键,或引入独立幂等层

35. 官方参考资料

以下资料是本文的主要依据,建议你后续按顺序继续看:

  1. Milvus 架构概览
    https://milvus.io/docs/zh/architecture_overview.md

  2. Data Processing
    https://milvus.io/docs/data_processing.md

  3. Collection Explained
    https://milvus.io/docs/ar/manage-collections.md

  4. Load & Release
    https://milvus.io/docs/load-and-release.md

  5. flush() API
    https://milvus.io/api-reference/pymilvus/v2.3.x/ORM/Collection/flush.md

  6. Index Explained
    https://blog.milvus.io/docs/index-explained.md

  7. Consistency Level
    https://blog.milvus.io/docs/consistency.md

  8. Primary Field & AutoID
    https://milvus.io/docs/primary-field.md

  9. Upsert Entities
    https://milvus.io/docs/upsert-entities.md

  10. In-Memory Replica
    https://blog.milvus.io/docs/replica.md

  11. Clustering Compaction
    https://milvus.io/docs/clustering-compaction.md

  12. Manage Milvus Connections
    https://milvus.io/docs/v2.3.x/manage_connection.md

  13. Scale Dependencies
    https://milvus.io/docs/scale-dependencies.md

  14. gRPC 官方站
    https://grpc.io/

  15. Milvus What is Milvus
    https://milvus.io/docs/id/overview.md


36. 你下一步最值得继续学的顺序

如果你打算真把 Milvus 学扎实,我建议按这个顺序继续:

  1. 先把本文第 4 到第 8 节吃透
    这是你眼下最缺的“segment / flush / load / index”主线。

  2. 再看你自己项目里的 schema 和导入脚本
    重点看主键设计、索引类型、flush 策略、去重时机。

  3. 然后补 Milvus 的一致性、Time Travel、Compaction
    这样你就不仅会用,还会理解系统为什么这么设计。

  4. 最后再上集群和 K8s
    因为如果基础对象没搞清楚,直接看集群很容易只剩名词。

如果你后面还想继续深入,最值得往下挖的方向一般有三个:

  • 项目实战线:结合你自己的导入脚本、Schema、索引参数逐段复盘
  • 性能调优线:批量导入、段管理、索引参数、查询延迟和召回率权衡
  • 分布式架构线:Milvus 集群、对象存储、消息流、Kubernetes 部署与扩展

37. 用一张图把 Milvus 集群架构看懂

如果你已经读到这里,差不多应该把零散名词拼起来了。下面这张图适合你在脑子里形成 Milvus 的整体结构。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
flowchart TB
Client[应用 / SDK / LangChain / 自研服务] --> Proxy[Proxy]

Proxy --> RootCoord[RootCoord]
Proxy --> QueryCoord[QueryCoord]
Proxy --> DataCoord[DataCoord]
Proxy --> QueryNode[QueryNode]

RootCoord --> ETCD[(etcd)]
DataCoord --> WAL[消息流/WAL]
DataCoord --> ObjectStore[(MinIO / S3)]
QueryNode --> ObjectStore
IndexNode[IndexNode] --> ObjectStore
DataNode[DataNode / StreamingNode] --> WAL
DataNode --> ObjectStore
QueryCoord --> QueryNode
DataCoord --> DataNode

这张图背后的核心思想其实不复杂:

  • Proxy 是统一入口
  • Coord 是控制平面,负责元数据、调度、拓扑和生命周期管理
  • Node 是执行平面,负责写入、查询、建索引
  • etcd 保存元数据
  • 对象存储 保存段文件和索引文件
  • 消息流 / WAL 负责写入流、顺序和恢复能力

你把它理解成“接入层 + 控制层 + 执行层 + 外部依赖层”,基本就不会乱。


38. 如果你做 RAG 或知识库,Milvus 通常处在什么位置

很多人第一次接触 Milvus,都是从 RAG 开始的。所以这里用最典型的知识库链路,把 Milvus 放回真实业务里看。

1
2
3
4
5
6
7
8
9
10
flowchart LR
Doc[原始文档 PDF/Word/网页] --> Chunk[文本切分 Chunking]
Chunk --> Embed[Embedding 模型]
Embed --> Milvus[Milvus 向量库]

UserQ[用户问题] --> QEmbed[Query Embedding]
QEmbed --> Milvus
Milvus --> TopK[召回 TopK 文档块]
TopK --> Rerank[可选 Rerank]
Rerank --> LLM[大模型生成答案]

这个链路里,Milvus 不负责:

  • 训练大模型
  • 理解文档语义本身
  • 生成最终答案

Milvus 负责的是:

  • 存储 chunk 的向量
  • 建索引
  • 根据 query 向量做近邻召回
  • 结合标量字段做过滤

所以如果你做 RAG,真正需要分清楚的是三层职责:

  1. Embedding 层:负责把内容变成向量
  2. Milvus 层:负责海量向量的组织、索引和召回
  3. LLM 层:负责把召回结果组织成最终答案

这也是很多团队容易犯错的地方:

  • 召回效果不好,就怀疑 Milvus
  • 实际根因可能是 chunk 切分策略错了
  • 或 embedding 模型不适合当前领域
  • 或 rerank 没做好

也就是说,Milvus 很重要,但它不是整条 RAG 链路的唯一变量。


39. Schema 和主键到底该怎么设计,才不容易后面返工

这一节非常工程化,但也非常值钱。

很多人建表时只想“先跑起来”,后面就会在这里反复返工。

39.1 先决定你的主键是“系统主键”还是“业务主键”

这一步决定了你后面能不能优雅地做:

  • 幂等写入
  • 精准删除
  • upsert
  • 去重

如果你用 auto_id=True

  • 好处是省事,Milvus 自动给你发 ID
  • 代价是你没法天然拿业务字段做 upsert 和幂等覆盖

如果你把业务唯一键设计成主键:

  • 更适合按业务维度去重和重写
  • 更适合增量同步
  • 但你要保证这个键真的稳定、唯一、长期可用

39.2 不要把 Partition 当成“高性能万能药”

很多人一看 Partition,就本能地想:

  • 那我按用户分区
  • 按日期分区
  • 按业务类型分区
  • 分得越细是不是越快

这往往是误区。

Partition 的主要价值是:

  • 逻辑隔离
  • 限定搜索范围
  • 某些特定业务维度下缩小检索域

如果你分得过细,反而会带来:

  • 管理复杂
  • 元数据更多
  • 加载和维护成本更高

所以通常建议是:

  • 没有强业务隔离需求时,不要一上来就大量分区
  • 优先把过滤交给标量字段和表达式
  • 只有当检索域天然可分、而且访问模式稳定时,再考虑 Partition

39.3 标量字段不是陪衬,它决定你后续过滤能力

向量检索不是只有向量字段。

一个可用的业务 Schema,通常除了向量,还应该至少考虑:

  • 业务主键
  • 文档 ID / 图片 ID
  • 类别或租户 ID
  • 更新时间
  • 状态位
  • 原始文本摘要或外部存储引用

这些字段会直接影响:

  • 过滤表达式怎么写
  • 更新删除怎么做
  • 召回结果如何映射回原始业务对象

一句很工程的话:

向量字段决定你能不能“找到相似”,标量字段决定你能不能“把相似结果用起来”。


40. 性能调优到底先抓什么,不要一上来只会调索引参数

很多人一说 Milvus 调优,就盯着 nprobeefMnlist。这些当然重要,但不是唯一抓手。

更靠谱的调优顺序通常是下面这样。

40.1 先确认问题到底是写入瓶颈、查询瓶颈,还是召回质量问题

这三类问题不要混着看:

  • 写得慢:可能是 batch 太小、flush 太频繁、对象存储慢、建索引策略不合适
  • 查得慢:可能是 load 策略、索引类型、查询并发、过滤条件太重
  • 查得不准:可能是 embedding 模型、索引参数、chunk 粒度、业务特征表达不够

40.2 写入优化,优先看这 4 件事

  1. 批量大小:太小会让 RPC、WAL、段管理开销被放大
  2. flush 频率:过于频繁会制造很多小 segment
  3. 索引构建时机:不要让导入、建索引、查询全都在最差时机挤在一起
  4. 对象存储与消息流性能:底层依赖慢,上层再怎么调也有限

40.3 查询优化,优先看这 5 件事

  1. 索引类型是否合适
  2. 数据是否已经 load 到查询节点
  3. 过滤条件是否过重
  4. TopK 是否设置过大
  5. 副本数和查询节点是否够用

40.4 不要忽略“工程上的假瓶颈”

很多所谓 Milvus 慢,最后排查出来其实是:

  • Python 客户端单线程瓶颈
  • 上游 embedding 接口慢
  • 文本切分太碎,导致检索数量暴涨
  • 下游 rerank 太慢
  • 应用层把本可缓存的查询每次都重新发

所以真正成熟的调优,不是只盯向量库,而是把整条链路一起看。


41. 生产上最容易踩的坑,我直接替你先踩一遍

最后这一节,我尽量写得更“像过来人”,因为这部分最适合博客读者收藏。

坑 1:把 Milvus 当成“扔进去就自动变智能”的黑盒

Milvus 很强,但它不替你解决:

  • embedding 模型选型
  • 文本切分策略
  • 业务主键设计
  • 数据清洗和去重

如果前处理错了,后面再怎么调索引参数都只是补救。

坑 2:还没理解主键,就急着上 upsert

只要你没有把“Milvus 主键”和“业务唯一键”的关系想清楚,upsert 就很容易变成误用。

坑 3:每导入一小批就手动 flush

这几乎是最典型的新手误区之一。

这样做短期看起来像“更稳”,长期看往往是:

  • 小 segment 变多
  • 后台 compaction 压力变大
  • 查询性能变差

坑 4:业务刚有一点量,就过早做过度分区

Partition 不是越多越好,很多系统最后不是被数据量拖垮,而是先被管理复杂度拖垮。

坑 5:只关注向量,不关注过滤字段和回表字段

最后搜索到了结果,却没法高效拿到:

  • 原始文本
  • 图片地址
  • 业务状态
  • 租户信息

这就会导致应用层还要再查一轮,链路又长又乱。

坑 6:只会“插入和查询”,不会看系统状态

真正工程上要养成的习惯是:

  • 看 segment 状态
  • 看 collection load 状态
  • 看索引构建状态
  • 看对象存储和消息流健康状况
  • 看查询延迟分布,而不是只看平均值

最后一句压轴建议

如果你真的想把 Milvus 学到工程可用,不要把它只当成一个 SDK,也不要把它神化成一个万能 AI 黑盒。最好的方式是:

  • 既理解它的数据模型
  • 也理解它的后台链路
  • 既知道它能做什么
  • 也知道它不能替你做什么

到这里,你对 Milvus 的理解,才算真的从“会用”慢慢走向“会设计、会排查、会解释”。