跳到正文

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     # 期望 OK

Python 侧:

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 内存、加载状态。这些决定了查询是稳定还是突然抖。

把一门技术讲透到既懂也会用