Milvus 作为一款开源的向量数据库,在推荐、图像检索和语义搜索等场景中应用广泛。但当数据规模增长到千万甚至上亿级别时,很多业务会明显感觉到查询响应变慢,尤其是高并发下的尾延迟难以控制。造成这种现象的核心因素往往不是机器性能不足,而是集合设计和索引策略不当。合理运用分区能力与索引调优手段,可以在不增加硬件成本的前提下显著改善查询效率。

分区设计如何缩小查询扫描范围
在 Milvus 中,集合(Collection)是存储向量的最外层容器,而分区(Partition)则是集合内部的逻辑子结构。如果没有显式创建分区,所有向量默认进入名为 _default 的分区。当执行带过滤条件的查询时,Milvus 仍可能加载全部分区对应的 segment 文件进行检索,这就带来了不必要的 IO 和计算开销。
按业务维度做分区是缩减扫描范围最直接的方式。例如日志向量化检索系统,可按天创建分区:partition_20240101、partition_20240102 等。查询某一天的数据时,只需在对应分区内搜索,其余历史分区完全不参与计算。对于多租户 SaaS 平台,用租户 ID 做分区也能实现物理隔离式的加速。
下面的 Python 示例展示了如何创建带分区的集合,并在插入与查询时指定分区:
from pymilvus import CollectionSchema, FieldSchema, DataType, Collection
fields = [
FieldSchema(name="id", dtype=DataType.INT64, is_primary=True),
FieldSchema(name="embedding", dtype=DataType.FLOAT_VECTOR, dim=128)
]
schema = CollectionSchema(fields, description="demo")
collection = Collection(name="face_vec", schema=schema)
# 创建按日期的分区
collection.create_partition(partition_name="p_20240101")
collection.create_partition(partition_name="p_20240102")
# 插入时指定分区
import random
data = [
[i for i in range(100)],
[[random.random() for _ in range(128)] for i in range(100)]
]
collection.insert(data, partition_name="p_20240101")
# 查询时仅搜索该分区
res = collection.search(
data=[[random.random() for _ in range(128)]],
anns_field="embedding",
param={"metric_type": "L2", "params": {"nprobe": 16}},
limit=10,
partition_names=["p_20240101"]
)
需要注意的是,分区数量并非越多越好。每个分区在底层会对应若干 segment,过多分区会导致元数据膨胀,反而让协调节点(Coordinator)在路由时变慢。通常建议单集合分区数控制在数百以内,并结合数据冷热程度做生命周期管理,将极冷数据归档或合并。
索引类型的选择决定检索复杂度
Milvus 支持多种索引类型,包括 FLAT、IVF_FLAT、IVF_PQ、HNSW、DISKANN 等。FLAT 是暴力检索,精度最高但耗时随数据量线性增长,仅适合百万级以下数据。IVF 系列基于倒排文件聚类,通过 nlist 将空间划分为多个桶,查询时只比对部分桶,从而换取速度。HNSW 是基于图的索引,内存占用高但查询极快,适合内存充裕的低延迟场景。
选择索引时要权衡精度、速度与资源。以 IVF_PQ 为例,它会对向量做产品量化压缩,大幅降低内存,但召回率略低于 IVF_FLAT。如果业务对准确率要求极高且内存有限,DISKANN 可将索引放在磁盘上,用较少内存支撑十亿级数据。下面的代码演示了在集合上构建 IVF_SQ8 索引并设置 nlist:
index_params = {
"metric_type": "L2",
"index_type": "IVF_SQ8",
"params": {"nlist": 1024}
}
collection.create_index(field_name="embedding", index_params=index_params)
collection.load()
索引建立后还需关注 nprobe 参数,它控制查询时探测的桶数。增大 nprobe 会提升召回率但拖慢查询,减小则相反。可通过离线评测在固定数据集上绘制 nprobe 与召回率、延迟的关系曲线,选出满足业务 SLA 的最优值。一般线上从 nlist 的 1% 到 5% 起步试探较为稳妥。
另外,索引并非一建永逸。数据持续写入后,segment 会不断密封并生成新索引,旧索引的统计信息可能失真。定期执行 compact 操作合并小 segment,并视情况重建索引,能避免查询性能随写入时间退化。
参数调优与监控验证的闭环
调优不是一次性工作,而要建立可度量的闭环。在预发布环境使用生产同构数据做压测,记录不同分区策略与索引参数组合下的 P99 延迟和 QPS。Milvus 自身提供了 metrics 接口,可对接 Prometheus 监控 query_latency、search_req_count 等指标,快速定位慢查询来自哪个集合或分区。
实践中常发现,单纯增大节点规格不如优化 nprobe 与分区过滤来得明显。例如某电商向量检索接口,原全分区查询 P99 为 1200 毫秒,按类目分区并将 nprobe 从 64 降到 32 后,P99 降至 280 毫秒,且召回率仅掉 0.5%。这说明业务可接受范围内,适当牺牲精度换延迟是划算的。
最后建议把调优经验沉淀为配置模板。新接入的业务线直接复用经过验证的分区规则和索引参数,后续仅根据数据量变化微调 nlist 比例。这样既能保持系统整体稳定,又让解决 Milvus 查询慢成为标准化动作而非救火式排查。