Milvus 核心功能:索引、度量、一致性与过滤
核心功能:索引、度量、一致性与过滤
Milvus 的能力可以拆成四块:用什么索引结构加速、用什么度量算相似度、允许多新的读、以及怎么把标量条件和向量检索组合起来。
索引类型
| 索引类型 | 原理简述 | 特点 | 适用场景 |
|---|---|---|---|
| FLAT | 不构建任何结构,查询时暴力计算全部距离 | 召回率最高,速度最慢,内存占用等于原始向量大小 | 数据量小(几万条以内)、对精度要求极高、或作为其他索引的召回率对照基线 |
| IVF_FLAT | 先用聚类把向量分到 nlist 个桶,查询时只探查最近的 nprobe 个桶 | 精度与速度可调,内存占用可控,召回率随 nprobe 上升 | 千万级以内、内存有限、需要精确控制召回率的场景 |
| IVF_SQ8 | 在 IVF 基础上把每个向量分量量化成 8 位整数 | 内存占用大约降到原始的三分之一到四分之一,精度有损失 | 内存吃紧、可以接受少量召回损失的大规模场景 |
| HNSW | 构建多层邻近图,查询时从稀疏的上层逐层下降到稠密的下层 | 查询延迟低、召回率高,代价是内存占用大、建索引慢 | 对延迟敏感、内存充足的在线检索,是很多线上的默认选择 |
| DiskANN | 基于图的索引,但把大部分索引数据放在 SSD 上,内存只缓存关键部分 | 用磁盘换内存,单机可支撑的数据量显著提升,延迟比纯内存索引高 | 数据量超过内存容量、又不想上分布式集群的场景 |
另外还有 AUTOINDEX,让 Milvus 根据数据特征自动挑一种索引,适合不想手工调参的起步阶段;以及若干 GPU 版本的索引,需要 GPU 环境支持。索引类型不是越多越好,同一批数据可以同时建多个索引,但每个索引都要占资源,按查询模式取舍即可。
度量方式
| metric type | 含义 | 取值的解读 | 什么时候用 |
|---|---|---|---|
| L2 | 欧氏距离 | 值越小越相似,完全不相似时值可以很大 | 向量已经归一化或者距离本身有物理意义时;图像、音频特征常用 |
| IP | 内积 | 值越大越相似,与向量模长相关 | 向量已归一化时,IP 与 COSINE 等价;推荐系统里常见 |
| COSINE | 余弦相似度 | 看方向不看长度,取值范围通常归一化到 -1 到 1 之间,越接近 1 越相似 | 文本 embedding 的主流选择,不受文本长短导致的向量模长差异影响 |
选择度量方式有一条硬性纪律:建索引和查询必须使用同一个 metric type。系统不会替你做换算,用 COSINE 建索引却用 L2 查询,得到的结果既不报错也没有意义,这类问题在线上很难被发现。
一致性级别
Milvus 把一致性做成了可调项,因为「读到自己刚写的数据」这件事在分布式系统里是有成本的。
- Strong:保证读到最新写入的数据。写入路径上的延迟会体现到查询上,适合对正确性要求高、并发不极端的场景,比如刚 upsert 完就要立刻回查。
- Bounded:在可容忍的一段时间窗口内保证读到较新的数据,是延迟与新鲜度之间的折中,常作为默认或推荐的线上选择。
- Eventually:只保证最终一致,查询延迟最低,但可能读不到最近刚写入的数据。适合日志、离线分析这类对新鲜度不敏感的批量检索。
较新版本的 Milvus 还提供了 Session 和 Consistent 等级,语义介于上述几档之间,具体定义以官方文档为准。实践中没必要全局用一个等级,可以对写入后的关键查询单独指定 Strong,其余走默认,避免整体性能被一个查询拖累。
标量过滤、分区与动态字段
实际业务里几乎没有「只按向量找」的需求,总是带着条件:只要这个租户的、只要没被删除的、只要发布时间在某个区间内的。Milvus 支持在 search 或 query 里传 filter 表达式,把标量条件与向量检索合并成一次请求,避免先在关系库筛完再逐个查向量。
过滤表达式的写法与常见编程语言接近,支持比较运算、逻辑组合(and、or、not)、集合成员判断(in)、以及字符串前缀匹配等。需要注意字段类型要匹配:整数和字符串不能用错,字符串字段在 schema 里必须声明 max_length。
分区是另一种收窄范围的手段,而且比过滤更「物理」——过滤是在候选集上再筛,分区是直接跳过整个子集不扫描。常见用法是按租户、按时间、按业务线切分。如果分区数量会随业务增长(比如按用户数分区),应该改用 partition key 机制,它能在写入时自动把数据路由到对应分区,避免手工建上千个分区。分区不是越多越好,每个分区都有独立的元数据和段管理开销。
动态字段(dynamic field)允许在 schema 之外写入额外的键值对,写入时以 JSON 形式落库,也可以在过滤表达式里访问。它的价值在于快速迭代期不必为每个新字段改 schema、重建 collection,代价是这些字段没有静态类型检查,过滤性能也不如显式声明的字段。等字段稳定下来,再规整进 schema 更划算。
关键调优参数
| 参数 | 所属索引 | 作用 | 调大之后 |
|---|---|---|---|
| nlist | IVF 系列 | 把向量空间聚成多少个桶 | 每个桶更小更精细,建索引更慢;桶太多会导致单桶内数据过少,反而降低召回 |
| nprobe | IVF 系列(查询期) | 查询时探查几个桶 | 召回率上升、延迟上升,是最常用的在线调节旋钮 |
| M | HNSW | 图中每个节点在每层保留的最大出边数 | 图更密、召回更高,内存占用与建索引时间同步上升 |
| efConstruction | HNSW(建索引期) | 建图时维护的候选队列长度 | 图质量更好,建索引变慢,一次性成本,对查询延迟无影响 |
| ef | HNSW(查询期) | 查询时维护的候选队列长度 | 召回上升、延迟上升;一般取 limit 的若干倍 |
调参的基本方法是:固定数据集和查询集,先建索引,然后扫一遍查询期参数(nprobe 或 ef),同时记录 recall 与延迟,画出曲线选拐点。建索引期的参数(nlist、efConstruction、M)决定了曲线的上限,查询期参数只是在曲线上选点。不要靠感觉猜参数,也不要把别人的参数直接照搬——最优值和数据分布、向量维度、查询模式强相关。

