主题
生产运维与容量规划
一句话:Milvus 上线后的问题大多不是「搜得准不准」,而是「内存够不够、段合不合理、故障能不能自愈」。
适用版本:Milvus 2.6.x。配置项默认值以官方 Configuration 文档和实际部署的 milvus.yaml 为准。
为什么需要
计算存储分离把单机的容量问题解决了,但把复杂度转移到了集群:查询节点要装下索引,数据节点要跟上写入,协调层要维护元数据,对象存储要扛住加载时的读放大。
任何一环算错,表现都不是「报错」,而是延迟抖动、加载超时、查询偶发失败。
核心机制
部署形态
| 形态 | 组件 | 适用 |
|---|---|---|
| Standalone | etcd + MinIO + 单进程 | 开发、验证、小规模 |
| Cluster(K8s) | Proxy、MixCoord、QueryNode、DataNode/StreamingNode、etcd、对象存储、WAL | 生产 |
生产底线:
- etcd 至少 3 节点,跨可用区部署;元数据丢失无法从对象存储反推。
- 对象存储用独立桶,开启版本控制或生命周期策略,避免误删。
- WAL 选型:Kafka/Pulsar 成熟但有额外运维;2.6 的 Woodpecker 可省掉外部依赖,新方案先在测试环境验证稳定性。
- 计算与存储分开扩:查询慢加 QueryNode,写入积压加 DataNode/StreamingNode,不要整集群一起扩。
容量测算(可套用的算法)
以 5000 万条、768 维、float32 为例:
原始向量 = 5000万 × 768 × 4 B ≈ 153.6 GB
HNSW 图 ≈ 每点 M×8 B 量级,M=16 时约 6.4 GB
标量列 ≈ 按字段估算,通常几 GB 到十几 GB
常驻内存 ≈ (153.6 + 6.4 + 标量) × 1.2(缓存与余量)≈ 200 GB结论:需要约 200 GB 内存用于加载,单节点放不下就拆成多个 QueryNode,或改用 IVF_SQ8 / 量化把原始向量压到 1/4。
QPS 侧不要用厂商 benchmark 直接套,按自己的 ef、limit、过滤条件压测出单节点 QPS,再算:
节点数 = ceil(峰值 QPS ÷ 单节点 QPS × 1.5)段、加载与合并
- 段是加载单位:一个 collection 的段越多,加载越慢、查询扇出越大。
- 小段来自高频小批量写入:持续小批量写入会不断产生小段,需要 compaction 合并。
- compaction 吃 IO 和 CPU:安排在低峰;大批量导入后先 compact 再建索引,通常更稳。
- load / release 是显式动作:不活跃的 collection 及时 release,否则一直占 QueryNode 内存。
备份与恢复
Milvus 的备份要同时覆盖两部分:
- 元数据:collection schema、索引、分区、别名(etcd)。
- 数据与索引文件:对象存储里的段文件。
官方 milvus-backup 工具做逻辑备份,把两者导出到另一个对象存储桶;恢复时导入到同版本或更高版本的集群。跨大版本恢复不保证兼容,升级前先做同版本恢复演练。
监控该看什么
| 层 | 指标 | 异常含义 |
|---|---|---|
| Proxy | 查询/写入延迟 P99、失败率、限流 | 入口过载或下游慢 |
| QueryNode | 内存使用、已加载段数、搜索延迟 | 内存不足会触发换页或 OOM |
| DataNode/Streaming | WAL 消费延迟、flush 延迟 | 写入积压,数据可见性变慢 |
| etcd | leader 变化、db 大小、慢请求 | 元数据层不稳,协调操作超时 |
| 对象存储 | 读带宽、请求延迟 | 加载慢、冷查询慢 |
只看进程存活没有意义,必须有一条「写入 → 检索」的端到端探针。
最小上手
集群用 Helm 安装(生产按官方 values 调整副本与资源):
bash
helm repo add milvus https://zilliztech.github.io/milvus-helm/
helm repo update
helm install my-milvus milvus/milvus \
--set cluster.enabled=true \
--set etcd.replicaCount=3 \
--set minio.mode=distributed
kubectl get pods | grep milvus日常检查与维护:
python
from pymilvus import MilvusClient
c = MilvusClient(uri="http://milvus:19530")
print(c.list_collections())
print(c.get_load_state("rag")) # Loaded / NotLoad
print(c.get_collection_stats("rag")) # 行数
job = c.compact("rag") # 低峰执行
print(c.get_compaction_state(job))备份(milvus-backup 需单独安装并配置 backup.yaml):
bash
milvus-backup create -n rag_20261024
milvus-backup list生产动作
- 上线前压测三件事:目标
ef下的 P99、过滤条件存在时的 P99、写入持续时的查询抖动。 - 内存留 30% 余量:growing 段、compaction、查询中间结果都会临时占用。
- 限制单次查询规模:
limit过大、offset过深都会放大归并成本;深分页改用游标或按主键续查。 - 写入批量控制在合理区间:过大导致单次请求超时,过小导致小段过多;按官方建议区间实测。
- 升级路径:备份 → 测试环境同版本恢复 → 升到目标版本 → 回归召回与延迟 → 生产滚动升级。2.6 的 MixCoord/StreamingNode 属于结构性变化,不要跳过小版本直接跨。
- 故障处置:
collection not loaded→get_load_state,确认索引已建完再load_collection。- QueryNode OOM → 看已加载段总大小与副本数,先 release 冷 collection,再扩节点。
- 查询变慢但 CPU 不高 → 检查对象存储读延迟、段数量、是否在 compaction。
- 写入成功但查不到 → 检查一致性级别与 growing 段是否已可查,别急着判定丢数据。
边界
- Milvus 不负责数据源真相:它存的是向量和副本,原始文档、权限、版本仍要在业务库;重建索引要从源头再灌一次。
- 对象存储不是无限快:冷数据首次加载会明显慢,分层存储降低了内存,但没有消除首次读盘。
- 多副本不是多活:QueryNode 副本提升查询可用性,不解决跨地域容灾;跨地域要自己做数据复制。
- 运维成本是真实成本:组件多、参数多,小团队没有 K8s 运维能力时,托管服务往往比自建更划算。
讲清楚
Milvus 的容量规划可以拆成两步:先算内存,再算节点。内存由向量原始大小、图结构、标量列和缓存余量组成,768 维 float32 下 5000 万条大约要 200 GB;放不下就用量化压缩,或者拆到多个 QueryNode。节点数则要用自己的 ef 和过滤条件压测出单节点 QPS,再乘安全系数。
上线后真正要盯的是段的数量和大小、QueryNode 内存、WAL 消费延迟、etcd 健康。段太碎会让查询扇出爆炸,内存不足会让加载失败,WAL 积压会让「写入成功但查不到」。
备份必须同时覆盖 etcd 元数据和对象存储文件,且要做恢复演练。Milvus 存的是可重建的索引副本,不是业务真相,这一点决定了它的容灾策略。