当数据规模、并发和运维要求一起上来时,向量库就不再只是一个索引文件
当单机索引不再够用
前面几篇讨论的很多概念,都可以在单机上成立。FAISS 能让你看清索引结构,Chroma 能让你快速完成原型,Qdrant 和 Weaviate 已经提供了相当成熟的服务能力。但在某些场景里,问题会继续升级:向量规模太大,单机内存放不下;写入量和查询量同时上升;需要副本、滚动升级和多节点扩容;还要把对象存储、元数据和索引构建拆分到不同角色上。这时,你面对的就不再是“选哪个索引”,而是“如何把向量检索作为分布式系统运行”。
MilvusMilvusMilvus is a distributed vector database designed for production-scale similarity search. It separates compute, storage, and coordination roles so large collections, indexing jobs, and query traffic can be scaled and operated across a cluster. 长期被放在这类问题的讨论中心。它的吸引力不只是索引支持面广,更在于它把向量检索放进了一个完整的分布式架构里思考。
Milvus 在架构上拆开了哪些事
如果只把向量库看成“一个能回答最近邻查询的程序”,就很难理解 Milvus 的设计。它的核心思路是把不同职责拆开:元数据协调、数据写入、索引构建、查询执行、对象存储和消息流转,都可以由不同组件承担。这样做的目的是让系统在规模上升后,不至于因为单个进程包揽一切而难以扩展。
在理解层面上,可以把它简化为三类角色。第一类负责协调和元数据管理,确保集合、分区、段和索引状态被一致地追踪。第二类负责数据流动,包括写入缓冲、刷盘、构建索引和把结果落到持久层。第三类负责查询服务,把请求路由到合适的数据分片并执行向量搜索。
Milvus 的完整部署通常还依赖外部组件,例如对象存储、键值元数据存储以及消息队列。这说明它不是一个“拷贝二进制就完事”的软件,而是一套围绕向量检索组织起来的系统。
Collection 与 Partition 不只是目录结构
使用 Milvus 时,最先接触的两个概念往往是集合collectionA collection in Milvus is the top-level logical container for vector data, schema, indexes, and search operations. It is roughly analogous to a table, but designed around vector fields and retrieval workloads. 与 分区partitionA partition is a logical subdivision within a collection. It helps organize data by business dimensions such as tenant, time window, or document domain, and can reduce the search scope when used carefully. 。如果只把 collection 当作“表”、partition 当作“子表”,理解还不够。
Collection 决定的是一套统一 schema 和索引策略下的数据边界。通常,同一 collection 里的向量维度、字段定义和索引方式是一致的。它适合表示同类型、同 embedding 策略的数据集合。例如一个客服知识库可以是一个 collection,一个图片相似检索库可以是另一个 collection。
Partition 则是同一 collection 内的逻辑切分。它经常被用来表达时间窗口、租户、业务域或冷热数据分层。合理使用 partition,可以减少查询时需要触达的数据范围,也有助于管理数据生命周期。但 partition 不是越多越好。分得过细会增加管理复杂度,甚至让调度与查询路径变得更重。
分布式系统里的“写入”远比想象复杂
单机实验里,向量写入通常就是一次 add() 调用。到了 Milvus 这样的系统里,写入要经过更多步骤。数据可能先进入缓冲区,再被组织成段,随后异步刷到持久层;索引构建也通常不是每写一条就立即完成,而是按段或批次处理。查询流量与构建流量之间还要相互隔离,避免大批量导入时把在线检索拖慢。
这正是分布式向量库和本地库最大的区别之一。前者不仅要“能查”,还要在持续写入、索引构建、故障恢复和副本同步中保持系统稳定。很多运维问题也是从这里开始出现的:段太碎、导入节奏不稳、索引构建排队过长、对象存储延迟异常,都会直接影响查询体验。
Helm 部署为什么重要
Milvus 经常运行在 Kubernetes 上,因此HelmHelmHelm is a package manager for Kubernetes. In the Milvus context it packages the deployment of coordinators, worker components, storage dependencies, and configuration into reproducible release definitions. 部署几乎是绕不开的话题。Helm 的价值不只是“安装方便”,更重要的是把多组件系统的参数、依赖和版本关系封装成可重复的发布方式。对于需要多环境、灰度升级和团队协作的系统,这一点非常关键。
一个典型的部署流程,大致会包含以下要素:准备 Kubernetes 集群;决定是使用外部对象存储和消息队列,还是采用 chart 内部附带的依赖;配置资源请求、持久卷与副本数;然后通过 Helm 安装或升级。
helm repo add milvus https://zilliztech.github.io/milvus-helm/
helm repo update
helm install milvus milvus/milvus \
--namespace milvus \
--create-namespace \
--set cluster.enabled=true
真实生产环境当然不会只写这一行参数。你还会关心对象存储地址、访问密钥、消息队列选择、节点亲和性、查询节点与数据节点的资源配额,以及监控栈如何接入。但 Helm 让这些复杂度至少被放进了一套可版本化的配置结构里。
Milvus 部署前最好先想清楚的三件事
Milvus 完整实战:从安装到带过滤的检索
1. 本地起一个 standalone Milvus(开发用)
生产环境是 cluster 模式,但开发测试单机 standalone 足够:
# 一键启动(Docker Compose)
curl -sfL https://raw.githubusercontent.com/milvus-io/milvus/master/scripts/standalone_embed.sh -o standalone_embed.sh
bash standalone_embed.sh start
# 验证:默认端口 19530(gRPC)+ 9091(管理面)
curl http://localhost:9091/healthz
Python 客户端:
pip install pymilvus sentence-transformers
2. 定义 Collection schema
跟 Qdrant 的灵活 payload 不一样,Milvus 强制定义 schema(更接近传统数据库):
from pymilvus import (
connections,
Collection,
CollectionSchema,
FieldSchema,
DataType,
utility,
)
from sentence_transformers import SentenceTransformer
model = SentenceTransformer("BAAI/bge-small-zh-v1.5")
DIM = 512
# 1) 连接
connections.connect(alias="default", host="localhost", port="19530")
# 2) 定义字段(schema)
fields = [
FieldSchema(name="id", dtype=DataType.INT64, is_primary=True, auto_id=True),
FieldSchema(name="vector", dtype=DataType.FLOAT_VECTOR, dim=DIM),
# 元数据字段——Milvus 也叫 scalar fields
FieldSchema(name="text", dtype=DataType.VARCHAR, max_length=2000),
FieldSchema(name="tenant_id", dtype=DataType.VARCHAR, max_length=64),
FieldSchema(name="category", dtype=DataType.VARCHAR, max_length=64),
FieldSchema(name="created_at", dtype=DataType.INT64), # Unix 时间戳
]
schema = CollectionSchema(fields=fields, description="Knowledge base")
# 3) 创建 collection
if utility.has_collection("kb"):
utility.drop_collection("kb")
coll = Collection(name="kb", schema=schema, shards_num=2)
shards_num 控制数据分片数——cluster 模式下会影响并行度,单机 standalone 设 2 够用。
3. 创建向量索引(必须!)
Milvus 跟 Qdrant 最大区别之一:collection 创建后还要单独建索引才能查。索引类型很丰富:
# 选项 1:HNSW(首选,召回 + 延迟双优)
index_params = {
"index_type": "HNSW",
"metric_type": "COSINE", # COSINE / L2 / IP
"params": {
"M": 16,
"efConstruction": 200,
},
}
# 选项 2:IVF_FLAT(大数据集 + 不想要 HNSW 的内存)
# index_params = {
# "index_type": "IVF_FLAT",
# "metric_type": "COSINE",
# "params": {"nlist": 1024},
# }
# 选项 3:IVF_PQ(极致省内存,召回略低)
# index_params = {
# "index_type": "IVF_PQ",
# "metric_type": "COSINE",
# "params": {"nlist": 1024, "m": 16, "nbits": 8},
# }
# 选项 4:DISKANN(向量放磁盘,查询时按需载入,适合超大数据)
# index_params = {
# "index_type": "DISKANN",
# "metric_type": "COSINE",
# "params": {},
# }
coll.create_index(field_name="vector", index_params=index_params)
# 把数据 + 索引载入内存(不 load 不能查)
coll.load()
Milvus 的索引类型矩阵(这是它比 Qdrant / Weaviate 都丰富的地方):
| 索引 | 召回 | 延迟 | 内存 | 适合数据规模 |
|---|---|---|---|---|
| FLAT | 100% | 高 | 高 | < 100 万(精确检索) |
| HNSW | 99% | 低 | 高 | 百万 ~ 亿级 |
| IVF_FLAT | 95% | 中 | 中 | 百万 ~ 千万 |
| IVF_SQ8 | 93% | 中 | 1/4 | 千万 ~ 亿 |
| IVF_PQ | 85~95% | 中 | 1/32 | 亿 ~ 十亿 |
| DISKANN | 95% | 中(首次慢) | 极低(向量在盘上) | 十亿 + 内存紧 |
| GPU_IVF_FLAT / GPU_CAGRA | 95% | 极低 | GPU 显存 | 需要 GPU 加速时 |
| SPARSE_INVERTED_INDEX | - | - | - | 稀疏向量(BM25 / SPLADE) |
4. 标量字段索引(过滤性能)
经常被过滤的元数据字段也要单独建索引:
coll.create_index(
field_name="tenant_id",
index_params={"index_type": "INVERTED"},
)
coll.create_index(
field_name="category",
index_params={"index_type": "INVERTED"},
)
5. 写入数据
import time
docs = [
{"text": "Python 异常 TypeError 通常因为对错误类型做操作",
"tenant_id": "team-a", "category": "python"},
{"text": "JavaScript 中 undefined 和 null 的区别",
"tenant_id": "team-a", "category": "frontend"},
{"text": "PostgreSQL 索引选型:B-tree、GIN、GiST 何时用",
"tenant_id": "team-b", "category": "database"},
{"text": "MongoDB 错误码 E11000 表示重复主键冲突",
"tenant_id": "team-b", "category": "database"},
]
texts = [d["text"] for d in docs]
vectors = model.encode(texts, normalize_embeddings=True).tolist()
now = int(time.time())
# Milvus 写入需要按字段顺序提供数据
coll.insert([
vectors, # vector 列
texts, # text 列
[d["tenant_id"] for d in docs], # tenant_id 列
[d["category"] for d in docs], # category 列
[now] * len(docs), # created_at 列
])
coll.flush() # 确保数据落盘并被索引
print(f"插入完成,总数 = {coll.num_entities}")
6. 检索:向量 + 过滤
q = "怎么解决重复主键的报错"
q_vec = model.encode([q], normalize_embeddings=True).tolist()
# 纯向量检索
results = coll.search(
data=q_vec,
anns_field="vector",
param={"metric_type": "COSINE", "params": {"ef": 64}}, # ef = HNSW 的 ef_search
limit=3,
output_fields=["text", "tenant_id", "category"],
)
for hit in results[0]:
print(f"score={hit.score:.3f} {hit.entity.get('text')}")
# 带过滤:Milvus 用 SQL-like 表达式
results = coll.search(
data=q_vec,
anns_field="vector",
param={"metric_type": "COSINE", "params": {"ef": 64}},
limit=3,
expr='tenant_id == "team-b" and category == "database"',
output_fields=["text"],
)
更复杂的过滤表达式:
expr = (
'tenant_id == "team-b" '
'and category in ["database", "backend"] '
'and created_at >= 1735689600 '
'and status != "draft"'
)
Milvus 的 expr 支持 ==, !=, <, <=, >, >=, in, not in, and, or, like,比 Qdrant 的结构化 Filter 更接近写 SQL。
7. 大批量写入:用 bulk_insert
# 普通 insert 适合 < 几千条;几万条以上用 bulk_insert(从 MinIO/S3 直接导入)
from pymilvus import utility
# 准备 parquet 文件传到 MinIO/S3
# 然后:
task_id = utility.do_bulk_insert(
collection_name="kb",
files=["s3://your-bucket/data.parquet"],
)
# 异步任务,可以查进度
state = utility.get_bulk_insert_state(task_id=task_id)
print(state)
bulk_insert 比 insert 快 10~50 倍,是亿级数据导入的标准方法。
Milvus 跟 Qdrant 的关键差异
| 维度 | Milvus | Qdrant |
|---|---|---|
| 架构 | 分布式(Coordinator + Worker 多角色) | 单二进制(也支持集群但更轻) |
| schema | 强 schema(必须先定义字段类型) | 灵活(payload 任意 JSON) |
| 索引类型 | 丰富(HNSW / IVF 全家 / DISKANN / GPU / Sparse) | HNSW(默认) |
| 依赖 | etcd + MinIO + Pulsar/Kafka | 无外部依赖 |
| 过滤 | SQL-like 表达式 | 结构化 Filter JSON |
| 生态 | Zilliz Cloud(官方托管) | Qdrant Cloud |
| 典型规模 | 千万 ~ 百亿 | 百万 ~ 数千万 |
| 运维 | K8s + Helm 是主流 | 单容器即可 |
| 何时选 | 真的需要分布式 / 多种索引 | 单机 ~ 中等规模生产 |
Standalone vs Cluster:什么时候上 Cluster
Standalone 够用的情况:
- 单个 collection 数据量 < 5000 万条
- QPS < 几百
- 不需要高可用 / 跨可用区
- 团队还没有 K8s 运维能力
必须上 Cluster 的信号:
- 数据量 > 1 亿(单机内存放不下)
- 需要 HA(任何节点挂掉服务不能断)
- 同时多个 collection 共享集群(多租户平台)
- 离线 bulk_insert 和在线查询要资源隔离
- 写入持续高吞吐(> 1 万条/秒)
Cluster 模式下,至少有:
- 1~3 个 Coordinator(rootcoord / datacoord / querycoord / indexcoord,新版合并成 mixcoord)
- N 个 QueryNode(吃查询流量)
- N 个 DataNode(吃写入流量)
- N 个 IndexNode(构建索引)
- 外部 etcd(元数据)+ MinIO/S3(对象存储)+ Pulsar/Kafka(消息流)
这个架构能力强,但运维门槛高一个数量级——只有真正用得上才值得上。
监控关键指标
无论 Standalone 还是 Cluster,Milvus 暴露 Prometheus 指标在 :9091/metrics。生产必看:
| 指标 | 含义 | 警戒值 |
|---|---|---|
milvus_proxy_search_latency | 查询延迟(P50/P95/P99) | P99 > 500ms 要警觉 |
milvus_querynode_load_segment_latency | QueryNode 段加载延迟 | 长期上升 → 段太碎,要 compact |
milvus_datanode_flush_buffer_op_count | DataNode 刷盘频率 | 持续高 → 写入压力大 |
milvus_indexnode_index_task_count | 索引任务队列 | 长期 > 0 → 索引追不上写入 |
milvus_storage_get_data_latency | 对象存储访问延迟 | > 50ms → S3/MinIO 是瓶颈 |
接 Grafana 用官方 dashboard 模板:grafana.com 搜 Milvus。
什么时候 Milvus 值得上场
Milvus 的优势,来自它在分布式规模上的可扩展性和系统化设计。它适合那些已经明确会遇到大规模向量数据、高并发检索、持续导入以及复杂运维需求的场景。例如企业级知识检索平台、图像搜索服务、推荐候选召回层,或者需要和对象存储、批处理流水线深度协作的系统。
但它并不适合作为所有项目的默认起点。对于几十万级别数据、单机可承载的原型验证或中小型内部工具,直接上 Milvus 常常会让问题重心从“效果验证”过早转向“平台维护”。分布式系统带来的并不只有能力,还有部署和运维成本。只有当这些能力确实被需要时,这个成本才是值得承担的。
用分布式视角重新理解向量数据库
学到这里,可以把向量数据库分成两个层次看。第一层是检索算法层,回答“怎样高效找到近邻”;第二层是系统工程层,回答“怎样把检索能力持续稳定地提供给生产流量”。Milvus 的重要性主要体现在第二层。它让你看到,向量库一旦进入生产,就和其他数据库一样,要面对存储、调度、复制、扩容、升级和故障恢复。
这也是为什么很多团队最后不是在 FAISS、HNSW 或 IVF 的公式细节上栽跟头,而是在“如何把索引当成服务长期运行”上暴露问题。Milvus 这类产品,正是为解决后者而存在。
本篇要点
- Milvus 的核心价值不只是支持向量检索,而是把它作为分布式系统组织起来。
- Collection 定义同一 schema 与索引策略下的数据边界,Partition 则提供 collection 内的逻辑切分。
- 分布式向量库里的写入涉及缓冲、段管理、刷盘和异步索引构建,远比单机
add()复杂。 - Helm 让多组件部署、升级与参数管理变得可重复,是 Milvus 运维中的关键入口。
- Milvus 适合真正有规模、并发和运维需求的场景,不适合所有项目一开始就采用。
下一篇
系列最后一篇将收束到选型与生产实践:什么时候该选专门向量数据库,什么时候 pgvector 就够用,如何做备份、监控、扩容和多租户隔离,以及它们在 RAG 流水线里最终承担什么角色。
参考资料
版权声明: 如无特别声明,本文版权归 sshipanoo 所有,转载请注明本文链接。
(采用 CC BY-NC-SA 4.0 许可协议进行授权)
本文标题:Milvus分布式向量库
本文链接:https://www.sshipanoo.com/blog/ai/vector-db/07-Milvus分布式向量库/
本文最后一次更新为 天前,文章中的某些内容可能已过时!