跳到正文

生产运维与容量规划 ​

一句话:Milvus 上线后的问题大多不是「搜得准不准」,而是「内存够不够、段合不合理、故障能不能自愈」。

适用版本:Milvus 2.6.x。配置项默认值以官方 Configuration 文档和实际部署的 milvus.yaml 为准。

为什么需要 ​

计算存储分离把单机的容量问题解决了,但把复杂度转移到了集群:查询节点要装下索引,数据节点要跟上写入,协调层要维护元数据,对象存储要扛住加载时的读放大。

任何一环算错,表现都不是「报错」,而是延迟抖动、加载超时、查询偶发失败。

核心机制 ​

部署形态 ​

形态组件适用
Standaloneetcd + 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 的备份要同时覆盖两部分:

  1. 元数据:collection schema、索引、分区、别名(etcd)。
  2. 数据与索引文件:对象存储里的段文件。

官方 milvus-backup 工具做逻辑备份,把两者导出到另一个对象存储桶;恢复时导入到同版本或更高版本的集群。跨大版本恢复不保证兼容,升级前先做同版本恢复演练。

监控该看什么 ​

层指标异常含义
Proxy查询/写入延迟 P99、失败率、限流入口过载或下游慢
QueryNode内存使用、已加载段数、搜索延迟内存不足会触发换页或 OOM
DataNode/StreamingWAL 消费延迟、flush 延迟写入积压,数据可见性变慢
etcdleader 变化、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 存的是可重建的索引副本,不是业务真相,这一点决定了它的容灾策略。

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