数据库基础体系 · 第 62/139 篇。文章以各产品官方稳定版本的公开语义为准;示例会明确引擎、事务与部署边界。
Milvus 完整基础:Collection、Segment、索引、查询和集群部署
Milvus 是面向向量检索的数据库系统。它不仅保存向量,还负责标量字段管理、过滤、索引构建、数据可见性、持久化、查询节点扩展和故障恢复。
理解 Milvus,不能只记住“把向量插入数据库,再调用搜索接口”。至少需要建立下面这条因果链:
- 用 Collection 定义数据模型;
- 插入数据后形成不同生命周期的 Segment;
- 对已封存的数据构建向量 索引;
- 查询节点同时处理新数据和已索引数据;
- 通过一致性级别、时间戳和加载状态决定查询能看到什么;
- 在独立模式或集群模式中,由不同组件完成协调、存储、索引和查询。
下文以 Milvus 2.x 官方公开语义为基础。具体参数和支持的索引类型应以所安装版本的官方文档为准,尤其是 Milvus Lite、Standalone 和 Distributed 的能力并不完全相同。
一、先建立模型:Milvus 中到底存储什么
一个典型的向量数据集可以表示为:
其中:
- 是主键;
- 是 维向量;
- 是若干标量元数据,例如文档类型、租户、时间、权限标签。
一次向量搜索通常是:
其中:
- 是查询向量;
- 是 Collection 中的数据;
- 是标量过滤条件;
- 返回与 最相似的 条记录。
Milvus 的职责不是简单执行一个 SQL ORDER BY distance LIMIT K。它还要处理:
- 数据仍在内存中、尚未形成稳定文件的情况;
- 已经封存但尚未完成索引的 Segment;
- 删除标记对搜索结果的影响;
- 多个 Segment 的局部 TopK 合并;
- 查询请求应该看到哪个时间点的数据;
- 分布式节点之间的结果合并与故障恢复。
二、Collection:逻辑数据集和 Schema
2.1 Collection 是什么
Collection 类似关系数据库中的表,但它不是关系表的直接替代品。
Collection 定义:
- 字段名称和字段类型;
- 主键;
- 向量字段的维度;
- 是否允许动态字段;
- 可选的分区键、分区和其他集合级属性。
常见字段类型包括:
INT8、INT16、INT32、INT64FLOAT、DOUBLEBOOLVARCHARJSONARRAYFLOAT_VECTORBINARY_VECTOR- 某些版本支持的其他向量类型
对于浮点向量,必须指定维度。例如:
则该字段的维度必须是 768。插入 767 维或 769 维向量都会失败。
主键可以是整数或字符串。auto_id=True 时,主键由 Milvus 生成;否则客户端必须提供主键。
2.2 一个可运行的 Collection 示例
下面示例使用 PyMilvus 的 MilvusClient 接口。它采用 Milvus Lite 的本地文件方式,适合学习 API 生命周期;生产集群应把 uri 换成服务器地址,并配置认证信息。
安装客户端:
python -m pip install -U pymilvus
完整示例:
from pymilvus import MilvusClient, DataType
client = MilvusClient(uri="./milvus_demo.db")
schema = client.create_schema(
auto_id=False,
enable_dynamic_field=False,
)
schema.add_field(
field_name="id",
datatype=DataType.INT64,
is_primary=True,
)
schema.add_field(
field_name="title",
datatype=DataType.VARCHAR,
max_length=256,
)
schema.add_field(
field_name="tenant_id",
datatype=DataType.INT64,
)
schema.add_field(
field_name="embedding",
datatype=DataType.FLOAT_VECTOR,
dim=4,
)
collection_name = "documents"
if client.has_collection(collection_name):
client.drop_collection(collection_name)
client.create_collection(
collection_name=collection_name,
schema=schema,
)
这里每一步都有明确约束:
id是主键,因此每条记录必须唯一;title是字符串,并且长度不能超过max_length;tenant_id可以用于标量过滤;embedding是四维浮点向量;enable_dynamic_field=False表示未声明的字段不能随意插入。
如果启用动态字段,未声明字段通常会进入特殊的动态字段结构。动态字段适合元数据经常变化的场景,但会降低 Schema 的显式约束能力,也可能使过滤和字段管理更复杂。稳定的数据模型通常应优先声明字段。
2.3 主键不是向量内容
主键只负责标识记录,不代表向量相似度。
下面两条记录的主键可以是任意不连续的整数:
rows = [
{
"id": 101,
"title": "Milvus introduction",
"tenant_id": 1,
"embedding": [1.0, 0.0, 0.0, 0.0],
},
{
"id": 205,
"title": "Vector indexing",
"tenant_id": 1,
"embedding": [0.9, 0.1, 0.0, 0.0],
},
]
主键 101 和 205 的大小关系不会影响向量距离。
2.4 Insert、Upsert、Delete 和事务边界
插入:
result = client.insert(
collection_name=collection_name,
data=rows,
)
print(result)
insert 的语义是写入新实体。若主键已经存在,不能把它当作关系数据库中的普通更新语句来理解。
更新通常使用 upsert:
client.upsert(
collection_name=collection_name,
data=[
{
"id": 101,
"title": "Milvus introduction, revised",
"tenant_id": 1,
"embedding": [1.0, 0.0, 0.0, 0.0],
}
],
)
upsert 的具体行为和限制应结合所用版本确认。工程上应明确主键写入策略,不要同时让多个业务流程无条件修改同一个主键。
删除可以按主键:
client.delete(
collection_name=collection_name,
ids=[205],
)
也可以按过滤表达式:
client.delete(
collection_name=collection_name,
filter="tenant_id == 1",
)
重要边界是:Milvus 不是提供通用多语句事务的关系数据库。以下操作不能自然地组成一个跨步骤 ACID 事务:
- 插入一批向量;
- 更新一批元数据;
- 删除另一批记录;
- 再修改一个外部业务数据库。
即使某次批量写入作为一个请求提交,也不能据此推断跨请求、跨 Collection、跨外部系统具备事务原子性。需要事务语义时,应在业务层设计幂等键、写入状态、补偿逻辑和版本字段。
三、Segment:Milvus 如何组织数据
3.1 Segment 的定义
Segment 是 Milvus 管理数据的一种物理组织单元。一个 Collection 的数据不会始终以一个整体文件存在,而会被划分为多个 Segment。
常见生命周期可以抽象为:
insert
↓
growing segment
↓ seal
sealed segment
↓ flush / 持久化
持久化的 sealed segment
↓ index build
带向量索引的 sealed segment
不同版本的内部状态和触发条件可能不同,但理解这三个概念最重要:
- Growing Segment:仍可能接收新写入,通常由查询节点以实时数据结构处理;
- Sealed Segment:不再接收新写入,可以进行稳定存储和索引构建;
- Indexed Segment:已针对某个向量字段构建索引,查询时可使用该索引。
Segment 不是业务上的分区,也不等同于 Collection 的 partition。Segment 通常由系统根据写入和调度策略自动管理,业务不应依赖某个 Segment 的编号或当前数量。
3.2 为什么不能每次插入都直接构建全局索引
假设 Collection 已有 条向量,每次新增一条就重建全局索引,成本可能接近:
其中 是向量维度。随着 增大,这种方式不可行。
Milvus 通常让新数据先进入 Growing Segment,达到条件后封存为 Sealed Segment,再在 Segment 级别构建索引。查询时:
- 对已有索引的 Sealed Segment 查询;
- 对尚未索引的 Sealed Segment 使用原始数据或临时结构;
- 对 Growing Segment 查询实时数据;
- 合并各 Segment 的候选结果;
- 应用删除标记和过滤条件;
- 返回全局 TopK。
这解释了一个常见现象:刚插入的数据可以在某种一致性设置下被查询到,但它不一定已经拥有与历史数据相同的持久化索引。
3.3 Flush 的含义和误解
flush 的作用是推动当前可持久化的数据形成稳定的存储状态,并触发后续处理。它不应被理解为:
- “立刻完成所有索引”;
- “立刻让所有客户端在任意一致性级别下都看到数据”;
- “把多个业务请求组成事务”。
在分布式部署中,写入通常经过消息流和对象存储等组件,索引构建也可能是异步的。因此:
insert 成功
≠ index 已完成
≠ 所有 QueryNode 已加载新索引
≠ 所有客户端立即可见
如果业务在导入完成后需要确认状态,应显式检查导入、Flush、索引和加载状态,而不是用固定睡眠时间猜测。
3.4 Compaction 和删除
Milvus 的删除通常不是立即改写所有向量文件,而是写入删除标记。查询时,删除标记会参与过滤;后台 Compaction 再把多个小 Segment 合并,清理已经无效的数据。
因此删除可能经历:
delete request
↓
产生删除标记
↓
查询路径过滤被删除实体
↓
后台 Compaction
↓
生成更紧凑的新 Segment
删除后立即观察底层对象存储文件,不能据此判断删除失败。反过来,如果删除量很大、Compaction 长时间积压,也可能导致存储空间和查询开销增加。
四、向量距离:索引和查询必须使用一致的度量
常见度量包括欧氏距离、内积和余弦相似度。
4.1 欧氏距离
距离越小越相似。
4.2 内积
分数越大越相似。若向量没有归一化,向量长度也会影响内积结果。
4.3 余弦相似度
它主要比较方向。若先把向量归一化为单位向量,则:
此时有:
所以单位向量上的 L2 排序与内积排序等价,但返回值的含义和数值范围仍不同。
4.4 一个完整算例
查询向量:
候选向量:
它们都已归一化。
余弦相似度为:
所以排序为:
a > b > c
L2 距离平方为:
排序仍然是:
a < b < c
反例是未归一化向量:
内积更偏好长度大的向量,而余弦相似度主要看方向。若模型训练和离线评测使用余弦相似度,在线却用未归一化向量的内积,结果可能系统性偏离。
索引创建时使用的 metric_type 必须与搜索时一致,向量预处理也必须一致。
五、向量索引:从精确搜索到近似搜索
5.1 FLAT:精确但需要扫描
FLAT 不建立近似结构。对每个查询向量,计算它与候选集合中所有向量的距离,再取 TopK。
若有 个 维向量,单查询计算量近似为:
它的优点:
- 结果可作为召回率基准;
- 不需要调节近似参数;
- 小数据集通常足够快。
缺点是数据规模增大后延迟和 CPU 成本线性增长。
5.2 IVF:先聚类,再搜索部分倒排列表
IVF 先通过聚类把数据分成 个簇。查询时:
- 计算查询向量与各簇中心的距离;
- 选择最近的 个簇;
- 只扫描这些簇内的向量;
- 合并候选结果。
若 ,它接近全量扫描;若 很小,速度更快,但可能漏掉真实近邻。
核心取舍是:
nlist 增大:
簇更细,单簇候选可能更少;
训练和管理成本增加。
nprobe 增大:
检查更多簇;
召回率通常提高,延迟也增加。
IVF_FLAT 保留原始浮点向量,因此精度主要由候选簇裁剪造成。
5.3 PQ:压缩向量以减少内存和计算
Product Quantization 将一个向量拆成若干子空间,再用较小的码本表示每个子向量。
设:
每个子向量由一个码字近似:
最终只保存码字编号 ,而不是完整的浮点数。
优点:
- 显著降低内存;
- 适合大规模向量;
- 可以利用压缩码快速估算距离。
缺点:
- 产生量化误差;
- 参数和训练数据分布敏感;
- 精度下降不一定能通过简单调大搜索参数完全恢复。
IVF_PQ 同时有两种近似:
- IVF 只搜索部分簇;
- PQ 用近似码表示向量。
因此需要分别评估 nprobe 和 PQ 参数的影响。
5.4 HNSW:图搜索
HNSW 为向量建立多层近邻图。查询从较高层开始快速接近目标区域,再逐层下降,在底层图上进行更精细的候选搜索。
常见参数含义:
M:每个节点维护的邻居数量;efConstruction:构建图时的候选搜索宽度;ef:查询时的候选搜索宽度。
通常:
M 增大:
图更密;
内存和构建成本增加;
可能改善召回率。
efConstruction 增大:
建图更慢;
图质量通常更好。
ef 增大:
查询更慢;
召回率通常更高。
HNSW 并不是“永远最快”。它通常需要较多内存,并且高写入、频繁删除、超大规模数据时,需要结合 Segment、Compaction 和资源配置评估。
5.5 DiskANN 和自动索引
某些 Milvus 版本和部署环境还支持 DiskANN 等面向磁盘或大规模数据的索引。它们对硬件、文件系统、内存、磁盘和版本有更强依赖。
AUTOINDEX 的含义是让 Milvus 根据配置和数据情况选择索引策略,而不是一个可以脱离版本和部署环境独立解释的算法名称。使用自动索引时,仍然要通过离线基准确认:
- Recall@K;
- p95/p99 延迟;
- 内存和磁盘使用;
- 构建时间;
- 写入期间的资源竞争。
5.6 召回率不能只看理论
用 FLAT 作为精确基线。对同一批查询,计算近似索引返回集合 与精确 TopK 集合 :
例如精确结果是:
[10, 11, 12, 13, 14]
近似结果是:
[10, 11, 12, 20, 21]
则:
反例是只测平均延迟:平均延迟可能很好,但少数租户、少数过滤条件或高并发下的 p99 延迟和召回率明显恶化。因此索引参数必须和真实查询分布一起评测。
六、创建索引、加载 Collection 和执行搜索
6.1 为向量字段创建 HNSW 索引
接着前面的代码,插入数据后创建索引:
index_params = MilvusClient.prepare_index_params()
index_params.add_index(
field_name="embedding",
index_type="HNSW",
metric_type="COSINE",
params={
"M": 16,
"efConstruction": 200,
},
)
client.create_index(
collection_name=collection_name,
index_params=index_params,
)
这里:
M=16控制图的邻居规模;efConstruction=200控制构建阶段的搜索宽度;COSINE必须与业务向量和查询方式一致。
并非所有部署模式都支持完全相同的索引集合。若当前版本或 Milvus Lite 不支持 HNSW,可使用该环境支持的索引类型,或先用 FLAT 验证数据和查询逻辑。
6.2 Load 的意义
创建索引并不等于查询节点已经可以使用它。通常还需要加载 Collection:
client.load_collection(collection_name)
可以把它理解为:
对象存储中的数据和索引
↓
QueryNode 加载到查询所需的内存或缓存
↓
查询服务可用
如果 Collection 未加载,常见失败表现包括:
- 查询报 Collection 未加载;
- 查询超时;
- 只有部分数据可见;
- 加载内存不足;
- 索引构建完成但搜索性能没有改善。
修改索引或数据后,加载状态和加载版本可能需要重新确认。不要把 create_index 的成功返回直接解释为所有 QueryNode 已完成加载。
6.3 向量搜索
search_result = client.search(
collection_name=collection_name,
data=[[1.0, 0.0, 0.0, 0.0]],
anns_field="embedding",
limit=2,
filter='tenant_id == 1',
output_fields=["title", "tenant_id"],
search_params={
"metric_type": "COSINE",
"params": {
"ef": 64,
},
},
)
for hits in search_result:
for hit in hits:
print(
"id=", hit["id"],
"distance=", hit["distance"],
"entity=", hit.get("entity"),
)
输入是一条四维查询向量,limit=2 表示返回两个结果。ef=64 是 HNSW 查询阶段的搜索宽度;如果使用 IVF,搜索参数应改为相应的 nprobe 等参数,不能混用。
搜索流程可以形式化为:
其中 是第 个 Segment。最终结果是:
只取每个 Segment 的局部 TopK 通常足以完成全局 TopK 合并,但过滤条件和删除标记必须在正确阶段处理,否则可能出现结果数量不足或结果错误。
6.4 标量 Query
如果不需要向量相似度,只需要按主键或标量条件读取数据,可以使用 query:
rows = client.query(
collection_name=collection_name,
filter="tenant_id == 1",
output_fields=["id", "title", "tenant_id"],
limit=10,
)
for row in rows:
print(row)
query 和 search 的差异是:
query:按过滤条件读取实体;search:按向量距离排序;- 混合检索:先进行向量搜索,再用标量条件约束候选范围。
如果业务需要全文、稀疏向量和稠密向量组合检索,还应使用对应版本支持的混合检索能力,不应把所有搜索都简化为一个 Dense Vector 字段。
七、数据可见性:一致性级别不等于事务隔离
7.1 为什么刚写入的数据不一定立即可见
分布式 Milvus 中,写入和查询由不同节点及不同内部组件处理。写入请求可能已经被接受,但查询节点还没有追上对应的数据时间戳。
因此必须区分:
- 写入请求是否成功;
- 数据是否已经持久化;
- 数据是否已经被索引;
- 查询节点是否已加载;
- 当前查询的一致性级别允许看到哪个时间点。
7.2 常见一致性语义
Milvus 提供多种一致性级别,常见概念包括:
- Strong:尽量读取最新可见数据,延迟和系统协调成本通常更高;
- Session:同一会话中保证相应的写后读语义;
- Bounded:允许一定时间延迟,在新鲜度和性能之间折中;
- Eventually:不保证立即可见,延迟通常更低。
具体默认值和客户端参数名称应以版本文档为准。
一致性级别解决的是“查询可以看到哪个时间点的数据”,不是完整事务隔离。例如:
事务 A:
插入向量
更新外部订单状态
查询 Collection
不能仅因为选择了 Strong,就推断外部订单状态和 Milvus 数据处于同一个事务快照中。
7.3 数据版本和删除的反例
如果一个实体刚刚被删除,而查询使用较弱的一致性级别,短时间内仍可能看到旧版本结果。若业务有严格的撤回要求,应:
- 设计业务版本号或有效时间;
- 查询时附加版本过滤;
- 在需要的位置使用更强的一致性;
- 对删除后的可见性进行实际验证。
不要只依赖“删除接口返回成功”。
八、Collection、Partition、Segment 的区别
这三个概念经常被混淆。
Collection
逻辑数据集和 Schema 的边界,类似一张面向向量的表。
Partition
Collection 内部的逻辑子集。可以按业务规则把数据写入不同 Partition,并在查询时指定 Partition。Partition 适合有明确路由条件的场景,例如时间范围或租户分组,但过多 Partition 会增加管理和查询调度复杂度。
Segment
系统内部的物理数据块,由写入、封存、Flush、Compaction 和索引流程产生。
关系可以表示为:
Collection
├── Partition A
│ ├── Segment 1
│ └── Segment 2
└── Partition B
├── Segment 3
└── Segment 4
Partition 是用户可控制的逻辑组织;Segment 是系统调度的物理组织。不要把每个租户创建成一个 Collection,也不要为了模拟分片而手工依赖 Segment。
九、Milvus 的组件和数据流
9.1 逻辑组件
Milvus 分布式架构通常包含以下职责:
- Proxy:接收客户端请求,负责路由、校验和部分结果协调;
- RootCoord:管理 Collection、Schema、数据库和全局元数据;
- DataCoord:管理数据 Segment、Flush、Compaction 和数据状态;
- QueryCoord:管理查询节点、Segment 分配和查询负载;
- IndexCoord:协调索引构建任务;
- DataNode:处理写入数据流并生成数据文件;
- QueryNode:加载 Segment、执行向量搜索和标量过滤;
- IndexNode:执行索引构建任务;
- etcd:保存元数据和协调状态;
- 对象存储:保存持久化数据、索引和日志文件;
- 消息存储:传递写入和时间戳相关的数据流。
不同发行版和版本可能把某些能力合并、拆分或交给外部组件,但职责划分大致如此。
9.2 写入路径
一次写入可以抽象为:
Client
↓
Proxy
↓
消息存储 / 写入协调
↓
DataNode
↓
Growing Segment
↓ seal
对象存储中的持久化文件
↓
IndexNode 构建索引
↓
QueryNode 加载
写入成功返回后,后台还可能继续进行:
- Segment 封存;
- 数据持久化;
- 索引构建;
- QueryNode 加载;
- Compaction。
9.3 查询路径
查询通常经过:
Client
↓
Proxy
↓
QueryCoord 分配查询任务
↓
QueryNode
├── Growing Segment
├── Sealed Segment
├── 向量索引
└── 标量过滤 / 删除标记
↓
局部结果合并
↓
Proxy 返回全局 TopK
查询延迟不仅取决于索引算法,还取决于:
- Segment 数量;
- 是否有大量 Growing Segment;
- Filter 选择性;
- QueryNode 是否发生缓存未命中;
- 并发和结果合并;
- Compaction、索引构建与查询之间的资源竞争。
十、Standalone、Milvus Lite 和 Distributed
10.1 Milvus Lite
Milvus Lite 适合:
- 本地开发;
- 单进程测试;
- 小规模离线实验;
- 验证 API 和数据模型。
它不等于生产分布式集群。它没有独立的 Proxy、QueryNode、DataNode 集群,也不提供分布式部署所需的高可用边界。
前面的:
client = MilvusClient(uri="./milvus_demo.db")
就是本地模式示例。它的主要价值是降低学习成本,而不是模拟生产故障和扩容行为。
10.2 Standalone
Standalone 通常把 Milvus 运行在单机或单实例环境中,适合:
- 开发测试;
- 中小规模部署;
- 不需要横向扩展的环境。
它仍然依赖持久化和协调组件的正确配置。单实例意味着关键服务故障可能直接影响整个服务,不应把 Standalone 当作自动高可用方案。
10.3 Distributed
Distributed 模式将协调、数据、索引和查询能力拆分为可扩展组件。适合:
- 大规模向量数据;
- 高并发查询;
- 持续写入和查询同时发生;
- 需要独立扩展 QueryNode、DataNode 或 IndexNode;
- 需要多实例故障容忍。
典型部署依赖:
Milvus 服务
├── Proxy
├── RootCoord
├── DataCoord
├── QueryCoord
├── IndexCoord
├── QueryNode
├── DataNode
└── IndexNode
外部基础设施
├── etcd
├── 对象存储
└── Kafka / Pulsar 等消息存储
使用 Kubernetes Helm 部署时,命令和 values 会随 Chart 版本变化。应先查看当前 Chart 的参数:
helm repo add milvus https://milvus-io.github.io/milvus-helm/
helm repo update
helm show values milvus/milvus > milvus-values.yaml
确认当前 Chart 对 Cluster、对象存储、消息存储、持久卷和资源限制的配置方式后,再安装。例如某些版本支持:
helm install milvus-cluster milvus/milvus \
--namespace milvus \
--create-namespace \
--set cluster.enabled=true
这条命令是否足够,取决于 Chart 默认值和外部依赖配置。生产部署不能仅复制命令,还必须检查:
kubectl get pods -n milvus
kubectl get svc -n milvus
kubectl get pvc -n milvus
kubectl describe pods -n milvus
kubectl logs -n milvus <pod-name>
<pod-name> 是实际查询结果中的 Pod 名称,执行时必须替换为真实名称。
10.4 部署时的故障边界
QueryNode 故障
通常可以重新调度查询任务并重新加载 Segment,但期间可能出现:
- 查询延迟升高;
- 可用副本不足;
- 加载内存不足;
- 某些请求失败或超时。
DataNode 故障
影响写入处理和数据落盘进度。若消息存储和持久化配置正确,已确认的数据通常可以通过日志和对象存储恢复;但未完成提交或外部依赖异常时,需要依据实际状态确认。
IndexNode 故障
通常影响索引构建进度,不一定影响已经加载完成的历史索引查询。新 Segment 可能暂时只能走未索引路径,导致查询延迟增加。
etcd 故障
会影响元数据和协调状态,是严重故障。必须为 etcd 配置持久化和高可用,而不是把它当作普通临时 Pod。
对象存储故障
会影响数据、索引和恢复能力。对象存储不是可选的“缓存”,其可用性直接影响持久化数据。
消息存储故障
会影响写入流、时间戳推进和部分实时查询语义。消息积压还可能造成数据延迟、Segment 封存延迟和资源压力。
十一、生产查询中的过滤、权限和多租户
向量相似并不等于用户有权读取。
例如一条文档可能与查询向量高度相似,但属于另一个租户。正确查询应把租户条件放入过滤表达式:
client.search(
collection_name="documents",
data=[[1.0, 0.0, 0.0, 0.0]],
anns_field="embedding",
limit=10,
filter="tenant_id == 42 and doc_type == 'manual'",
output_fields=["id", "title", "tenant_id"],
search_params={
"metric_type": "COSINE",
"params": {"ef": 64},
},
)
但过滤能否达到理想性能,取决于:
- 过滤字段是否存在;
- 条件选择性;
- Segment 数据分布;
- 版本和索引类型对过滤的执行方式;
- 向量候选和标量过滤的先后路径。
安全边界仍应放在服务端:
- Milvus 认证和网络访问控制;
- Collection 或数据库级权限;
- 应用层租户校验;
- 不允许客户端任意修改
tenant_id; - 对
output_fields做白名单控制。
不要只依赖客户端传入的 tenant_id 过滤条件。若调用方可以自由改写过滤表达式,过滤就不是权限控制。
十二、导入、版本和备份的实际边界
12.1 批量导入与在线写入不同
少量实时数据适合 insert。大规模离线数据通常应考虑批量导入机制,以减少客户端逐条请求、降低网络往返和写入调度压力。
批量导入完成后仍需确认:
- 导入任务状态;
- Segment 是否生成;
- 索引是否完成;
- Collection 是否加载;
- 召回率和查询延迟是否符合预期。
“导入任务成功”不自动等于“在线查询已经使用新索引”。
12.2 数据版本必须由业务管理
Embedding 模型升级后,同一文本可能产生完全不同的向量空间。旧向量和新向量直接混搜通常没有明确的几何意义。
可以使用版本字段:
embedding_model = "model-v2"
embedding_dim = 768
embedding_created_at = ...
查询时过滤同一模型版本,或建立新 Collection 完成迁移后切换流量。不要仅依赖 Collection 名称中的字符串约定而不记录实际模型和预处理配置。
12.3 备份不能只备份对象存储
生产恢复至少要考虑:
- Collection 和 Schema 元数据;
- 主键与标量字段;
- 原始文档或可重新生成向量的来源;
- 向量数据和索引;
- etcd 元数据;
- 对象存储;
- 消息存储中尚未完成处理的数据;
- 认证、网络和部署配置。
索引通常可以从向量数据重建,因此备份策略可以在“备份索引文件”和“恢复后重建索引”之间权衡。但重建时间、计算成本和业务恢复时间目标必须实际测量。
十三、常见失败表现和诊断顺序
13.1 插入报维度错误
原因通常是:
- 模型输出维度与 Schema 不一致;
- 某批数据被错误截断或拼接;
- 稠密向量和稀疏向量字段混用。
诊断:
print(len(rows[0]["embedding"]))
并检查模型配置、预处理代码和 Collection Schema。
13.2 搜索返回空结果
常见原因:
- Collection 未加载;
- 查询了错误的 Collection 或 Partition;
- Filter 把所有数据排除了;
- 查询向量字段名称错误;
- 数据尚未达到当前一致性级别的可见时间点;
- 主键删除标记已经生效。
应依次检查集合是否存在、加载状态、实体数量、过滤表达式和一致性设置,而不是先调大 ef 或 nprobe。
13.3 搜索结果明显不准
诊断顺序通常是:
- 用 FLAT 建立精确基线;
- 确认索引与搜索使用同一度量;
- 确认向量归一化策略一致;
- 检查过滤条件;
- 调整 HNSW 的
ef或 IVF 的nprobe; - 评估是否是 PQ 量化误差;
- 检查不同模型版本是否混在一起。
如果 FLAT 结果也不符合业务预期,问题通常不在 ANN 索引,而在 Embedding 模型、文本切分、归一化、语言分布或标签定义。
13.4 查询延迟突然升高
可能原因包括:
- Growing Segment 数量增加;
- 新数据尚未完成索引;
- QueryNode 内存不足导致加载或缓存压力;
- Compaction 与索引构建抢占 CPU、磁盘或内存;
- Filter 选择性差;
- 查询并发超过 QueryNode 能力;
- Segment 数量过多,结果合并成本增加。
应同时观察:
- 查询 p50、p95、p99;
- QueryNode CPU、内存和磁盘;
- Segment 状态;
- 索引构建和 Compaction backlog;
- 消息积压;
- 对象存储和 etcd 延迟。
只增加副本不一定有效。如果瓶颈在索引构建、对象存储、消息存储或标量过滤,扩展 QueryNode 可能只是掩盖问题。
十四、从本地示例到生产系统的最小迁移路径
一个较稳妥的迁移过程是:
- 使用 Milvus Lite 验证 Schema、Insert、Search、Query 和 Delete;
- 使用 Standalone 验证持久化、索引构建、Load 和重启恢复;
- 使用接近生产规模的数据建立 FLAT 基线;
- 选择 HNSW、IVF、PQ 或其他索引并测量 Recall@K;
- 在生产拓扑中分别压测写入、查询、Compaction 和索引构建;
- 验证 QueryNode、DataNode、IndexNode、etcd、对象存储和消息存储的故障恢复;
- 建立数据版本、备份、权限和成本监控;
- 最后再进行流量切换。
最容易犯的错误,是把“本地单进程 API 能运行”当成“分布式生产部署已经验证”。本地模式只能证明调用方式和部分数据逻辑正确,不能证明高可用、扩容、数据恢复、实时可见性和高并发性能正确。
Milvus 的核心可以归纳为:Collection 定义逻辑数据模型,Segment 承载数据生命周期,索引在精确度、内存和延迟之间做取舍,QueryNode 合并实时与历史数据,协调组件负责状态推进,而集群部署的可靠性取决于 Milvus 服务和外部元数据、对象存储、消息存储共同组成的完整数据链路。
系列导航与关联阅读
- 系列入口:数据库完整学习路线:从关系模型、事务索引到分布式与向量检索
- 上一篇:pgvector 实战:PostgreSQL 向量类型、HNSW、IVFFlat 与混合查询
- 下一篇:Qdrant 实战:Collection、Payload 过滤、HNSW、分片和快照
- 延伸:向量索引原理:Flat、HNSW、IVF、PQ 的精度、内存和延迟
- 延伸:向量数据库生产运维:摄取、版本、评测、备份、权限和成本
官方资料
本文依据数据库官方文档重新梳理;正文、示例与生产检查清单由 WR BLOG 编写。

评论
0 条讨论