Milvus从入门到入土
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 = 123name like 'apple%'price > 10
但向量检索的核心问题是:
- “这个文本和哪些文本语义最像?”
- “这张图片和图库中哪些图片最相似?”
- “这个专利 claims 向量最接近哪些历史专利?”
这时你存的不是一句话本身,而是一个高维向量,比如:
- 384 维
- 768 维
- 1024 维
然后你的查询不再是 SQL 精确比较,而是:
- 计算余弦相似度
COSINE - 计算欧氏距离
L2 - 计算内积
IP
如果你把 1 亿条向量都暴力全扫,肯定慢。所以 Milvus 的核心价值就是:
- 把向量和元数据组织成数据库里的结构
- 给向量建适合 ANN 的索引
- 用分布式方式存、管、查
- 在召回速度、召回率、成本之间做工程平衡
3. Milvus 的基础对象
3.1 Collection
Collection 可以理解为“表”。
官方文档的定义是:Collection 是一个二维表,固定列、可变行;列叫 field,行叫 entity。
例子:
Collection_MainCollection_ClaimsCollection_Images
你项目里也就是这么用的:
3.2 Field
Field 就是列。
可以是:
- 标量字段:
VARCHAR、INT64、BOOL、JSON、ARRAY - 向量字段:
FLOAT_VECTOR、SPARSE_FLOAT_VECTOR
例如你的 Collection_Claims:
application_numberclaim_numberclaim_textclaim_vector_denseclaim_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
主键可以是:
INT64VARCHAR
3.5 AutoID
如果启用 auto_id=True,Milvus 自动给你生成主键。
优点:
- 导入简单
- 不用自己维护唯一 ID
缺点:
- 你的业务主键不再等于 Milvus 主键
upsert、delete(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 的思路是:
- 先把新数据写入 growing segment
- 累积到一定条件后 seal
- 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 的区别
11.1 Search
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 的官方架构描述为主,可以理解为:
- 客户端通过 SDK 或 REST 把写入请求发给 Proxy
- Proxy 做请求校验
- Proxy 按 shard 路由把数据送到对应 Streaming Node
- Streaming Node 为数据分配时间戳 TSO
- Streaming Node 先把操作写入 WAL
- 数据进入 growing segment
- 在 segment 满足条件或 flush 后,growing 变 sealed
- Data Node 对 sealed segment 做离线处理,比如 compaction、建索引
- 索引文件和日志快照写入对象存储
- Query Node 加载 sealed segment 及其索引,参与稳定查询
- Coordinator 做 handoff,把实时态转为历史态
你要把这条链子记成一句话:
写入链路 = Proxy 接请求 -> StreamingNode 记 WAL -> Growing Segment 吃数据 -> Flush/Seal -> DataNode 建索引 -> QueryNode 加载查询
13. 查询一条向量时,Milvus 后台发生了什么
同样按官方主线理解:
- 客户端发起 search / query
- 请求先到 Proxy
- Proxy 根据路由信息找到相关 shard / 节点
- 对 growing 数据,由负责该 shard 的节点查实时数据
- 对 sealed 数据,由 Query Node 查历史数据和索引
- 各节点返回局部 topK
- Proxy 做归并和后处理
- 返回最终结果
这说明一件事:
一次查询不只是“查一个索引文件”,而是一个分布式归并过程。
所以面试里你可以说:
“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 是分布式系统,所以“刚写进去的数据,立刻查,必须看到吗?”这个问题就变成一致性问题。
官方文档给了四种一致性等级:
StrongBoundedSessionEventually
默认一般是 Bounded。
17.1 Strong
尽量保证读到最新写入。
代价:
- 等待更多同步
- 延迟可能更高
17.2 Eventually
更偏最终一致。
好处:
- 更快
代价:
- 刚写入的数据可能暂时查不到
17.3 Session
更适合单会话里“我自己写的数据,我最好能看见”。
17.4 Bounded
在新鲜度和延迟之间折中。
这也是生产里常见的默认选择。
面试里的回答方式:
“Milvus 不是单机库,而是分布式向量数据库,所以它把一致性做成了可配置项。你可以在读到最新数据和查询延迟之间做权衡。”
18. Upsert 到底是什么,为什么很多人会误用
官方定义很明确:
upsert是“如果主键存在就更新,不存在就插入”- 它是按 primary key 判断的
这是第一重点。
不是按:
application_numberfile_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
但更正确的思考顺序应该是:
- 业务唯一键是什么
- Milvus 主键是什么
- 去重发生在 embedding 之前还是之后
- collection 是否已经 load
- flush 是否过于频繁
- segment 会不会被打碎
- 索引是在后台增量补建还是被你手动反复重建
这才是工程上真正影响成本和性能的顺序。
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 API9091: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 被拆成四层:
- Access Layer
- Coordinator
- Worker Nodes
- Storage
其中:
- Access Layer:Proxy
- Coordinator:协调器,负责拓扑、调度、一致性
- Worker Nodes:Streaming Node、Query Node、Data Node
- Storage:etcd、Object Storage、WAL
22.2 老版本 / 传统面试常见名词
在很多资料和旧版本部署里,你会看到这组名字:
proxyroot coorddata coordquery coordindex coorddata nodequery nodeindex 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 依赖:
MinIOKafkaPulsaretcd
新版还有 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 句
- Milvus 是向量数据库,不是 embedding 模型。
- Collection 像表,Field 像列,Entity 像行。
- Partition 是逻辑分组,Shard 是并行写入切片,Segment 是物理数据块。
- 新数据先进入 growing segment。
- growing 变成 sealed,意味着这块数据不再写、适合查、适合建索引。
flush()会促使 segment seal,频繁 flush 会制造很多小 segment。load()是把数据和索引加载到内存供查询,不是重建索引。upsert是按主键工作的,不是按普通业务字段工作的。- Milvus 的核心架构是 Proxy + Coordinator + Worker Nodes + Storage。
- etcd 管元数据,MinIO/S3 管对象和索引,WAL 保证写入顺序与恢复。
34. 如果你正在做导入或检索项目,最后给你 4 条真正有用的建议
无论你做的是专利检索、RAG 知识库、图片相似搜索,最重要的都不是“看见 Milvus 就立刻想着上什么索引”,而是先问下面几个更根本的问题:
- 你的业务唯一键到底是什么?
- 这个唯一键是不是 Milvus 主键?
- 去重发生在云端 embedding 之前还是之后?
- 你是不是在频繁 flush,把 segment 打碎了?
大多数项目里,优先级通常是:
- embedding 前做去重
- 避免多线程重复处理同一个业务 ID
- 不要频繁 flush
- 如果未来真要 upsert,就把业务唯一键设计成 Milvus 主键,或引入独立幂等层
35. 官方参考资料
以下资料是本文的主要依据,建议你后续按顺序继续看:
-
Milvus 架构概览
https://milvus.io/docs/zh/architecture_overview.md -
Data Processing
https://milvus.io/docs/data_processing.md -
Collection Explained
https://milvus.io/docs/ar/manage-collections.md -
Load & Release
https://milvus.io/docs/load-and-release.md -
flush() API
https://milvus.io/api-reference/pymilvus/v2.3.x/ORM/Collection/flush.md -
Index Explained
https://blog.milvus.io/docs/index-explained.md -
Consistency Level
https://blog.milvus.io/docs/consistency.md -
Primary Field & AutoID
https://milvus.io/docs/primary-field.md -
Upsert Entities
https://milvus.io/docs/upsert-entities.md -
In-Memory Replica
https://blog.milvus.io/docs/replica.md -
Clustering Compaction
https://milvus.io/docs/clustering-compaction.md -
Manage Milvus Connections
https://milvus.io/docs/v2.3.x/manage_connection.md -
Scale Dependencies
https://milvus.io/docs/scale-dependencies.md -
gRPC 官方站
https://grpc.io/ -
Milvus What is Milvus
https://milvus.io/docs/id/overview.md
36. 你下一步最值得继续学的顺序
如果你打算真把 Milvus 学扎实,我建议按这个顺序继续:
-
先把本文第 4 到第 8 节吃透
这是你眼下最缺的“segment / flush / load / index”主线。 -
再看你自己项目里的 schema 和导入脚本
重点看主键设计、索引类型、flush 策略、去重时机。 -
然后补 Milvus 的一致性、Time Travel、Compaction
这样你就不仅会用,还会理解系统为什么这么设计。 -
最后再上集群和 K8s
因为如果基础对象没搞清楚,直接看集群很容易只剩名词。
如果你后面还想继续深入,最值得往下挖的方向一般有三个:
- 项目实战线:结合你自己的导入脚本、Schema、索引参数逐段复盘
- 性能调优线:批量导入、段管理、索引参数、查询延迟和召回率权衡
- 分布式架构线:Milvus 集群、对象存储、消息流、Kubernetes 部署与扩展
37. 用一张图把 Milvus 集群架构看懂
如果你已经读到这里,差不多应该把零散名词拼起来了。下面这张图适合你在脑子里形成 Milvus 的整体结构。
1 | flowchart TB |
这张图背后的核心思想其实不复杂:
- Proxy 是统一入口
- Coord 是控制平面,负责元数据、调度、拓扑和生命周期管理
- Node 是执行平面,负责写入、查询、建索引
- etcd 保存元数据
- 对象存储 保存段文件和索引文件
- 消息流 / WAL 负责写入流、顺序和恢复能力
你把它理解成“接入层 + 控制层 + 执行层 + 外部依赖层”,基本就不会乱。
38. 如果你做 RAG 或知识库,Milvus 通常处在什么位置
很多人第一次接触 Milvus,都是从 RAG 开始的。所以这里用最典型的知识库链路,把 Milvus 放回真实业务里看。
1 | flowchart LR |
这个链路里,Milvus 不负责:
- 训练大模型
- 理解文档语义本身
- 生成最终答案
Milvus 负责的是:
- 存储 chunk 的向量
- 建索引
- 根据 query 向量做近邻召回
- 结合标量字段做过滤
所以如果你做 RAG,真正需要分清楚的是三层职责:
- Embedding 层:负责把内容变成向量
- Milvus 层:负责海量向量的组织、索引和召回
- 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 调优,就盯着 nprobe、ef、M、nlist。这些当然重要,但不是唯一抓手。
更靠谱的调优顺序通常是下面这样。
40.1 先确认问题到底是写入瓶颈、查询瓶颈,还是召回质量问题
这三类问题不要混着看:
- 写得慢:可能是 batch 太小、flush 太频繁、对象存储慢、建索引策略不合适
- 查得慢:可能是 load 策略、索引类型、查询并发、过滤条件太重
- 查得不准:可能是 embedding 模型、索引参数、chunk 粒度、业务特征表达不够
40.2 写入优化,优先看这 4 件事
- 批量大小:太小会让 RPC、WAL、段管理开销被放大
- flush 频率:过于频繁会制造很多小 segment
- 索引构建时机:不要让导入、建索引、查询全都在最差时机挤在一起
- 对象存储与消息流性能:底层依赖慢,上层再怎么调也有限
40.3 查询优化,优先看这 5 件事
- 索引类型是否合适
- 数据是否已经 load 到查询节点
- 过滤条件是否过重
- TopK 是否设置过大
- 副本数和查询节点是否够用
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 的理解,才算真的从“会用”慢慢走向“会设计、会排查、会解释”。



