在构建高性能、高可用性的向量搜索服务时,弹性伸缩能力至关重要。Milvus作为主流的向量数据库,其查询性能主要依赖于Query Node的数量。当业务量增长需要增加Query Node时,用户最关心的问题是:数据重平衡(Rebalance)过程是否会引起在线业务的瞬时抖动?
本文将深入解析Milvus的Segment Rebalance机制,并提供实操指南,确保您在进行集群扩容时实现零抖动。
1. 理解Query Node与数据段(Segment)
在Milvus的分布式架构中:
1. Data Node负责将写入的数据打包成不可变的Segment,并将其持久化到对象存储(如MinIO/S3)。
2. Query Node负责从对象存储加载Segment,并在内存中构建索引,以响应搜索和查询请求。
Segment是Query Node进行查询的最小单位。当集群规模变化时,协调者(DataCoord/RootCoord)需要确保所有Query Node均匀地加载和分布Segment,这就是重平衡的目的。
2. Milvus重平衡(Rebalance)的非破坏性机制
Milvus的设计哲学是高可用优先,因此其重平衡过程采用了非破坏性(Non-Disruptive)或Copy-and-Serve的策略,以避免服务中断。
关键机制:先加载,后卸载
当增加一个新的Query Node (QN_New) 时,重平衡过程通常遵循以下步骤:
- 协调者触发分配: RootCoord/DataCoord 发现新的 QN_New 加入,计算需要迁移的 Segment(S1),并决定将 S1 从旧节点 (QN_Old) 迁移到 QN_New。
- 新节点加载: QN_New 从对象存储中下载 S1 的元数据和索引文件,并在本地完成索引加载。
- 双节点服务期(Dual Serving): 在此阶段,Segment S1 同时在 QN_Old 和 QN_New 上被加载。协调者知道 S1 可以通过这两个节点查询。
- 确认和通知: QN_New 完成加载后,通知协调者。
- 旧节点卸载: 协调者通知 QN_Old 卸载 Segment S1。只有在新节点完全确认可服务后,旧节点才会停止服务。
核心结论: 由于在切换期间,Segment S1 至少被一个节点(通常是两个节点)加载并提供服务,业务流量不会感知到数据丢失或查询失败,从而实现了零抖动。
3. 实际操作:扩容Query Node与观察
我们以基于Kubernetes/Helm部署的Milvus为例,展示如何安全地进行扩容,并观察其状态。
步骤一:查看当前的Query Node数量和配置
假设我们使用Helm进行部署,当前Query Node副本数为2。
# 检查当前部署状态
$ helm get values milvus-release -n milvus
# 假设当前配置如下:
# queryNode:
# replicas: 2
步骤二:增加Query Node副本数
我们将副本数从2增加到3。这是触发重平衡的直接操作。
# 使用helm upgrade命令进行扩容
$ helm upgrade milvus-release milvus/milvus --namespace milvus \
--set queryNode.replicas=3 --reuse-values
# 观察Pod状态,确认新的Query Node已启动
$ kubectl get pods -n milvus | grep querynode
# milvus-release-querynode-5b... Running 1/1
# milvus-release-querynode-6c... Running 1/1
# milvus-release-querynode-7d... Running 1/1 <- 新加入的节点
步骤三:监控重平衡过程
虽然Milvus客户端无法直接观察到Segment在节点间的字节流传输,但我们可以通过监控Milvus集群的内部指标和业务指标来验证零抖动。
A. 内部指标监控(Segment状态)
新的Query Node加入后,协调者会向其发送LoadSegment请求。您可以通过Prometheus和Grafana监控以下关键指标:
| 指标名称 | 含义 | 期望表现 |
|---|---|---|
| milvus_querynode_segment_count | Query Node上加载的Segment数量 | QN_New数量逐渐上升,QN_Old数量略微下降或保持不变 |
| milvus_querynode_segment_size | Query Node上加载的Segment总大小 | 同上,新节点总大小逐渐增加 |
B. 业务指标监控(关键性能指标,KPIs)
在扩容过程中,持续监控您的在线搜索服务的延迟和吞吐量。
# 假设使用Python客户端进行持续的压力测试
import time
from pymilvus import connections, utility
# 建立连接
connections.connect("default", host="milvus-proxy", port="19530")
# 模拟高频查询,并在扩容期间持续记录延迟
collection_name = "my_vector_collection"
start_time = time.time()
total_queries = 0
latencies = []
while time.time() - start_time < 300: # 持续运行5分钟,覆盖扩容周期
try:
# 假设这是一个耗时的搜索操作
# 记录查询时间
query_start = time.time()
# results = utility.load_collection(collection_name) # 实际应是search操作
time.sleep(0.01) # 模拟查询时间
query_end = time.time()
latencies.append((query_end - query_start) * 1000)
total_queries += 1
time.sleep(0.001)
except Exception as e:
# 记录任何查询失败
print(f"Query failed: {e}")
# 分析延迟数据
import numpy as np
print(f"Total queries: {total_queries}")
print(f"P99 Latency during scale-up: {np.percentile(latencies, 99):.2f} ms")
期望结果: 在整个扩容和重平衡过程中,P99 延迟曲线应保持平稳,不会出现明显的尖峰(即瞬时抖动)。如果有抖动,通常是由于新节点的硬件配置、网络带宽或索引构建的CPU竞争导致的,而不是重平衡机制本身带来的服务中断。
4. 总结与最佳实践
Milvus在增加Query Node时,其数据重平衡过程是高度可靠且非破坏性的。它通过确保Segment在新旧节点上同时提供服务,直到新节点完全就绪,从而保证了在线业务的零抖动。
最佳实践建议:
- 预热时间: 确保新的Query Node有足够的时间加载数据和构建索引。对于超大型Segment,这个过程可能需要几分钟。
- 资源隔离: 避免在新节点加载数据时,立刻对其施加过高的业务查询压力。
- 监控网络I/O: Segment加载涉及到从对象存储下载数据,网络带宽饱和是导致加载时间过长和潜在抖动的主要外部因素,应重点监控Query Node的网络I/O。
汤不热吧