主题
Milvus 架构与核心概念
一句话:Milvus 把向量数据库拆成「接入、协调、计算、存储」四层,用对象存储换容量、用计算节点换弹性,代价是组件多、链路长。
适用版本:Milvus 2.6.x。2.6 引入 Streaming Node、Woodpecker WAL、MixCoord、分层存储与 RaBitQ,旧版本的组件名与职责略有差异;新特性的默认开关以 Release Notes 为准,升级前先在测试环境验证。
为什么需要
单机向量库的天花板很明确:索引必须常驻内存,数据量一涨就要换机器;写入和查询抢同一份资源;扩容只能整机复制。
Milvus 的做法是把状态和计算分开:
- 数据落在对象存储(S3/MinIO/OSS),容量近乎无限、成本低。
- 索引和查询放在 QueryNode,按负载加减节点。
- 元数据放 etcd,保证协调层看到同一份视图。
- 写入先落 WAL,保证节点故障后能重放。
代价是每次查询都要跨网络取段、加载索引,延迟比单机库高;组件多了,故障面也大了。
核心机制
四层结构
一次写入:
一次查询:
关键点:写入成功不等于可查。段要刷盘、建索引、被 QueryNode 加载,才进入检索路径;growing 段虽然可查,但走的是暴力扫描。
数据模型
- Collection:schema 定义字段类型、维度、主键。
- Partition:查询时可裁剪;适合按时间/业务线划分,不适合高基数租户。
- Partition Key:按某个标量字段哈希分片,专为多租户设计,见 03 篇。
- Segment:检索与合并的最小单位。段太小会导致查询扇出大,段太大导致加载慢、合并重。
一致性级别
| 级别 | 语义 | 代价 |
|---|---|---|
| Strong | 读到最新写入 | 延迟最高 |
| Bounded(默认) | 容忍约 5 秒的滞后(graceful_time) | 延迟与新鲜度折中 |
| Session | 能看到自己的写入 | 跨客户端不保证 |
| Eventually | 最快,可能读到旧数据 | 一致性最弱 |
写后立即查的场景要么用 Strong,要么在业务层做「写完等索引追上」的补偿。多数 RAG 场景用 Bounded 就够。
最小上手
Docker Compose 起单机版(Windows/macOS/Linux 通用):
bash
# 官方 standalone compose(版本号按需替换)
curl -L -o docker-compose.yml \
https://github.com/milvus-io/milvus/releases/download/v2.6.0/milvus-standalone-docker-compose.yml
docker compose up -d
curl http://localhost:9091/healthz # 期望 OKPython 侧:
python
from pymilvus import MilvusClient, DataType
client = MilvusClient(uri="http://localhost:19530")
schema = client.create_schema(auto_id=False, enable_dynamic_field=True)
schema.add_field("id", DataType.INT64, is_primary=True)
schema.add_field("text", DataType.VARCHAR, max_length=1024)
schema.add_field("vector", DataType.FLOAT_VECTOR, dim=3)
index_params = client.prepare_index_params()
index_params.add_index(
field_name="vector", index_type="HNSW", metric_type="COSINE",
params={"M": 16, "efConstruction": 200},
)
client.create_collection("demo", schema=schema, index_params=index_params)
client.insert("demo", [{"id": 1, "text": "退款政策", "vector": [0.1, 0.2, 0.3]},
{"id": 2, "text": "物流时效", "vector": [0.9, 0.1, 0.1]}])
res = client.search("demo", data=[[0.1, 0.2, 0.3]], limit=2,
output_fields=["text"],
search_params={"metric_type": "COSINE", "params": {"ef": 64}})
print(res)Milvus Lite(MilvusClient("./milvus.db"))可以本地跑,但只支持能力子集,且主要面向 Linux/macOS;Windows 建议直接用 Docker。
生产动作
- 部署形态:单机(standalone)适合验证与小规模;生产用集群(Helm / milvus-operator),etcd 至少 3 节点。
- 加载与释放:collection 不 load 不能查;不活跃的 collection 及时
release,否则白占内存。 - 段管理:关注段数量与大小;过多小段会让查询扇出爆炸,靠 compaction 合并,但不要在查询高峰触发。
- 容量测算:内存 ≈ 向量数据 × (1 + 索引开销) + 标量列 + 查询缓存。先把「热数据」算清楚,冷数据用分层存储。
- 监控:Proxy 的查询延迟与失败率、QueryNode 内存与段数、DataNode 消费延迟、etcd 健康、对象存储 IO。
- 健康检查:
/healthz只说明进程活着,不能替代查询链路验证;要有一条端到端探针(建集合→写入→检索)。
边界
- 不是 OLTP 数据库:没有多行事务、复杂 join、强约束;元数据和向量的一致性靠应用层。
- 强一致有明确代价:Strong 会把查询压到最新段,延迟上升;高 QPS 下不要默认开。
- etcd 是元数据单点域:3 节点是底线,丢了元数据比丢段文件更麻烦。
- 对象存储延迟会影响加载与冷查询:本地盘/高性能对象存储的差别在冷启动时最明显。
- 组件多:单机版适合验证,生产集群的运维成本要提前算进去。
讲清楚
Milvus 的核心是计算存储分离。向量和标量以段为单位放在对象存储,索引和查询在 QueryNode,元数据在 etcd,写入先过 WAL。这样做的直接好处是容量不受单机磁盘限制、查询节点可以按 QPS 加减;代价是查询要跨网络取段,组件多、链路长。
数据模型是 Database → Collection → Partition → Segment。段是检索和合并的单位,growing 段走暴力扫描,sealed 段才走索引,所以「写入成功」和「立刻可查」之间隔着刷盘、建索引、加载三步。一致性级别就是在这三步上做取舍,默认 Bounded 容忍约 5 秒。
生产上最该盯的不是进程活着,而是段的数量和大小、QueryNode 内存、加载状态。这些决定了查询是稳定还是突然抖。