概述
学习资料:
中文文档
Milvus是zilliz推出的开源、高可用性、高性能和易扩展性的向量数据库,其目标是为大规模相似性检索和AI应用提供支持。建立在Faiss、Annoy等开源向量搜索库的基础上,能够支持数十亿甚至万亿级别的向量检索。Milvus还支持数据分片、数据持久化、流式数据摄取、混合搜索(包括矢量和标量数据)等许多高级功能。可部署在Kubernetes上,以获得最佳的可用性和弹性。在Milvus 2.0版本中进一步实现存储和计算分离的架构,所有组件都是无状态的,以增强其弹性和灵活性。Milvus遵循数据层和控制层分解的原则,被设计为包含4个层次:接入层、协调器服务、工作节点和存储。在扩展或灾难恢复方面,这些层是相互独立的。
特点:
- 检索速度快:在万亿向量数据集上进行检索,可达到毫秒级的延迟;
- 简化非结构化数据的管理:API丰富,在笔记本电脑、本地集群和云上都能为用户带来一致的体验;
- 可靠性强:数据的复制和故障恢复功能;
- 高度可扩展性:可根据负载类型,在组件级别进行自动扩缩容,提高资源调度效率;
- 多种检索方式:除向量以外,还支持存储整数、浮点数等数据类型,每条数据也可容纳多个字段,可实现基于标量过滤和向量检索相结合的搜索方式;
- 为了平衡时效性和效率,将流式处理和批处理相结合;
概念
几个核心概念:
- 位集:一组由0和1组成的位数数组,可用来紧凑高效地表示特定数据,而不是使用整数、浮点数或字符。位数默认为0,只有满足特定要求时才会被设置为1。
- 集合:相当于RDBMS中的表,用于存储和管理实体。
- 实体:由一组表示现实世界对象的字段组成,每个实体由唯一的主键表示。
- 字段:组成实体的单元,可以是结构化数据(如数字、字符串)或向量。
- 分区:集合的一个划分。Milvus支持将集合数据在物理存储上分成多个部分。这个过程称为分区,每个分区可以包含多个段。
- 模式:定义数据类型和数据属性的元信息。每个集合都有自己的集合模式,定义集合的所有字段、自动ID(主键)分配功能和集合描述。集合模式中还包括定义字段名称、数据类型和其他属性的字段模式。
- 分片:指将写操作分配给不同节点,以充分利用集群的并行计算能力来写入数据。默认情况下,一个集合包含两个分片。采用基于主键哈希的分片方法,提供随机和自定义分片。
- 段:由Milvus自动创建用于保存插入数据的数据文件。一个集合可以有多个段,一个段可以有多个实体。在向量相似性搜索过程中,会扫描每个段并返回搜索结果。段可以是增长或封存状态。增长段保持接收新插入的数据,直到被封存。封存段不再接收任何新数据,并将被刷新到对象存储中,留下新数据插入到一个新创建的增长段。增长段将被封存,要么是因为它所持有的实体数量达到预定义阈值,要么是因为增长状态的时间跨度达到指定的限制。
- 通道,有两个不同的通道:
- PChannel:物理通道,每个PChannel对应一个日志存储的主题,默认情况下,启动Milvus集群时将被分配一组256个PChannel用于存储记录数据插入、删除和更新的日志;
- VChannel:逻辑通道,每个VChannel对应一个集合中的一个分片,每个集合将被分配一组VChannel用于记录数据插入、删除和更新。VChannel在逻辑上是分隔的,但在物理上共享资源。
- 日志
- 日志序列:记录所有改变Milvus中集合状态的操作;
- 日志代理:一种支持回放的发布-订阅系统,负责流式数据持久化、可靠的异步查询执行、事件通知和查询结果返回。确保在工作节点从系统故障中恢复时增量数据的完整性;
- 日志快照:一个二进制日志,是段中记录和处理Milvus向量数据库中数据更新和更改的较小单位。来自一个段的数据会被持久化到多个binlog中。有三种类型的binlog:InsertBinlog、DeleteBinlog和DDLBinlog;
- 日志订阅者:订阅日志序列以更新本地数据,并以只读副本形式提供服务;
- 消息存储:日志存储引擎。
- 归一化:指将嵌入(向量)转换为其范数等于一的过程。如果使用内积(IP)来计算嵌入的相似性,所有嵌入都必须进行归一化。归一化后,内积等于余弦相似度。
- 依赖:Milvus依赖于其他组件,包括etcd(存储元数据)、MinIO或S3(对象存储)和Pulsar(管理快照日志)。
相似度计算方法
名称 | 说明 |
---|---|
欧氏距离(L2) | 衡量多维空间中两个点之间的绝对距离 |
内积(IP) | 计算两个向量之间的点积,可以反映向量之间的方向差异 |
汉明距离(Hamming) | 表示两个相同长度的字符串在相同位置上不同字符的个数 |
存储引擎
索引
索引类型的具体名称和参数可能依赖于Milvus的版本以及底层存储引擎。
Milvus支持的索引类型有两大类:
- 标量索引:用于过滤条件的加速查询;适用于非向量数据(如整数、浮点数、字符串等),Milvus 2.x开始支持对标量字段进行索引
- 向量索引:用于加速高维向量相似性搜索。
标量索引
常见的标量索引类型包括:
- Hash:哈希索引,适用于精确匹配查询。对等值过滤非常高效;
- Sorted:排序索引,适用于范围查询。对数值型或时间类型字段,可利用排序索引进行快速的范围过滤。
- Trie:
- SLOT_SORT
- BITMAP
- INVERTED:倒排索引,默认自动为标量字段创建,加速等值查询和范围查询;支持高效过滤,例如在混合查询中先通过标量索引筛选数据,再执行向量搜索。
此外,部分场景可能隐式使用布隆过滤器(Bloom Filter)用于快速判断数据是否存在,减少无效磁盘访问。
向量索引
常见的向量索引类型包括:
- FLAT:直接进行暴力搜索(线性扫描),没有额外的索引结构,不压缩向量,保证100%召回率,但查询速度较慢;适用于数据量较小(百万级)的情况。
- IVF(Inverted File)系列
- IVF_FLAT:将向量聚类后,在每个簇内使用FLAT方式进行搜索;
- IVF_PQ:在IVF基础上使用乘积量化(Product Quantization)来压缩向量,牺牲少量精度换取高压缩比,适合大规模数据;
- IVF_SQ8:使用标量量化(Scalar Quantization)对向量进行压缩,IVF的变体,适合内存敏感场景。
- HNSW:Hierarchical Navigable Small World,基于多层图结构的索引,支持高效近似搜索,查询速度快但内存占用较高,需调整参数ef和M。
- ANNOY:基于随机投影和树结构构建索引,适用于内存较充足的场景,能提供高效的近似搜索。
- RNSG:Reediting Navigable Small Graph,基于改进的图结构,优化搜索路径,适用于高召回率场景(较新版本可能支持)。
- SCANN:
- DiskANN:基于磁盘的索引,支持超大规模数据集,将高频数据缓存在内存,低频数据存储在磁盘(需确认版本支持)。
- GPU索引
- GPU_CAGRA
- GPU_IVF_FLAT
- GPU_IVF_PQ
- GPU_BRUTE_FORCE
适用场景:
- 低延迟:HNSW、IVF系列
- 高精度:FLAT、IVF_FLAT
- 内存优化:IVF_SQ8、IVF_PQ
- 大规模数据:DiskANN、IVF_PQ
配置
配置文件milvus.yaml
,各个功能模块,从基础设施(存储、消息队列)到业务功能(查询、写入、索引),参数加起来超过500多个。
配置参数大致可分为三类:
- 依赖组件:包括三大外部组件,etcd、minio、mq;
- 自身组件:包括proxy、rootcoord、querycoord、querynode、indexnode、datacoord、datanode;
- 其他功能:包括log、security、quotaAndLimits。
关于etcd的核心配置(用户需要关注的配置):
etcd.endpoints
:etcd服务地址,默认使用Milvus自带的etcd服务,支持接入自搭建的etcd集群,稳定性会更高;etcd.rootPath
:存储Milvus元数据的key的前缀名,默认不需要修改。有多套Milvus集群想要使用同一套etcd服务时,就可修改不同的前缀名,来做集群元数据隔离;etcd.auth
:milvus默认不开启etcd验证;用于连接开启安全验证的外部etcd服务。
minio,支持对接各种OSS,如MinIO、S3、GCS、其他云OOS,核心配置:
minio.address/minio.port
:minio.bucketName
:如果有多套Milvus集群想要使用同一个minio服务时,可以通过bucketName来隔离。minio.rootPath
:minio.cloudProvider
mq核心配置:
pulsar.address/pulsar.port
:pulsar的服务地址,支持外部pulsar服务;pulsar.tenant
:pulsar租户名,当有多套milvus集群想要共用一套pulsar,可通过不同的租户名来隔离;msgChannel.chanNamePrefix.cluster
:如果不想使用pulsar的多租户来做隔离,可通过修改message channel的前缀名来做隔离。
支持Kafka作为mq。
rootcoord,主要用来处理DDL、DCL请求以及时间戳服务TSO的管理,核心配置:
rootCoord.maxPartitionNum
:一个集合中允许创建的最大集合数。如果租户数超过1024,就不推荐使用partition来做隔离,推荐使用partition key方案,租户数可达到百万以上。rootCoord.enableActiveStandby
:coord节点,不能直接做横向扩展,需要开启ActiveStandby模式后才可以扩展多节点来做容灾。
Proxy是Milvus用来接收和验证请求以及做结果合并的组件,核心配置:
proxy.maxFieldNum
:Milvus的一个集合里面可以创建多个标量字段和向量字段,但是这些字段的总数不建议超过64,字段数过多,系统的存储压力和检索压力都会比较大;proxy.maxVectorFieldNum
:在进行多向量字段搜索时,一个集合中的向量字段数不能超过10个,主要在一些多模态检索,多因子认证的场景会用到;proxy.maxShardNum
:Milvus在创建集合时需要设置shard_num,shard可以理解数据进入Milvus的通道。目前建议每2亿条数据设置一个shard。2亿数据以内,设置一个shard,2-4亿数据设置2个shard,以此类推;proxy.accessLog
:默认关闭,打开可监测Milvus收到请求的详细信息,包括发送用户,ip地址,调用接口,sdk信息等。
Querycoord负责监控管理querynode的状态以及节点间的负载均衡,配置基本都是内部使用,用户无需关注太多。
querynode核心配置:
queryNode.mmap
:mmap开关。对于有很多标量字段的collection,如果把数据都存放到内存,成本肯定会很高,milvus提供的mmap能力,可以很好的解决这个问题。缺点:在(内存/检索数据)比过低的情况,搜索性能会急剧下降,需要根据场景来评估。
Indexnode也没有用户侧需要关注的参数。
datacoord主要负责数据段(数据分片,segment)的大小、生命周期,以及压缩管理,同时负责数据的垃圾回收。indexcoord的功能也合并进datacoord,datacoord还负责create index,indexnode索引任务调度等职责。重点参数:
dataCoord.segment.maxSize
:控制集群中数据分片的最大尺寸(in-memory index)。机器内存资源允许,参数越大,segment数量会越少,查询速度也会更好。单个querynode节点128G内存,将dataCoord.segment.maxSize
设置为8G,获得的性能比1G的dataCoord.segment.maxSize
有四倍左右的提升;dataCoord.segment.diskSegmentMaxSize
:控制集群中数据分片的最大尺寸(diskann index)。和dataCoord.segment.maxSize
相似,不过主要是针对disk index;dataCoord.segment.sealProportion
:growing segment转成sealed segment的系数。dataCoord.segment.maxSize
和dataCoord.segment.sealProportion
共同决定segment何时转化为sealed状态。对于数据更新比较频繁,且希望尽快创建好索引的场景,参数可设置小一点,如0.12。对于一些偏离线场景,对性能要求不高,参数可设置大一些,如0.3~0.5,节省索引节点开销;dataCoord.segment.expansionRate
:compaction过程中,segment允许的膨胀倍数,Milvus允许存在的最大Segment Size = maxSize * expansionRate
;dataCoord.gc.dropTolerance
:segment compaction产生新的segment,老的segment就会被清理;drop collection后,collection里的segment也会被清理。但不会立即删除这些待清理的segment释放存储,而是会等gc时间到之后才会物理清除。dataCoord.gc.dropTolerance
用于控制gc等待时间。
Datanode是负责数据写入落盘的节点。
log配置:
log.level
:生产环境建议使用info级别日志。测试环境建议用debug级别,日志会非常多,开启debug等级后务必注意磁盘空间用量;log.file
:Milvus的日志默认会打印到标准输出,同时也支持配置成文件的形式存储,对于文件的形式支持设置最大文件size,最长生命周期以及最多备份文件数。
security
Milvus支持用户鉴权和RBAC,配置在common模块下:
common.security.authorizationEnabled
:安全验证的控制开关,默认关闭;common.security.defaultRootPassword
:打开安全验证后,Milvus默认root用户密码。
quotaAndLimits
Milvus提供非常多的限流的配置,包括写入、删除、查询等,重点:
quotaAndLimits.flushRate.collection
:控制collection Flush操作的频率,默认0.1是指每10秒只允许执行1次Flush操作。Flush操作会做两件事,将growing segment转化为sealed segment,以及将mq里的数据落盘到对象存储上。如果过于频繁地调用Flush,系统会产生大量的小segment,给系统造成较大的compaction压力,进而影响查询性能。Flush一般是在插入完一批数据,不再继续插入时,可以手动调用一次,加快数据落盘和索引建立。在Milvus内部,growing segment size达到maxSize * sealProportion
,便会自动seal,同时系统也会每10分钟自动做seal segment的落盘。大部分情况下,不需要去手动调用Flush。数据的可见性和Flush没关系,是由查询的一致性等级来决定的,每插入一次数据,都会调一次Flush,导致系统整体性能非常差,并且影响稳定性。quotaAndLimits.upsertRate/quotaAndLimits.deleteRate
:控制upsert和delete的速率。Milvus采用LSM Tree作为其底层数据结构,更新和删除操作过于频繁,会给系统compaction任务产生压力。两者的速率建议都不要超过0.5MB/s。如果确实需要快速地更新一个集合中的大部分数据,建议通过collection alias的方式,将新数据写入新集合,更新完毕后,将alias指向新集合。
案例分析
Case 1:Performance First
对于性能要求比较高的场景,一般都会选择图索引,如HNSW,DISKANN。一般会同时配合调节如下参数:
- 调高
dataCoord.segment.maxSize
,dataCoord.segment.diskSegmentMaxSize
。根据机器配置情况,可调大到4G或8G。调高maxSize
要求更高的IndexNode和QueryNode运行时内存,且引入更多的写放大,所以务必使用较大规格的机器; - 调低
dataCoord.segment.sealProportion
,dataCoord.segment.expansionRate
,将单个Growing Segment maxSize控制在200MB左右,减轻Delegator(QueryNode中的leader节点)的内存压力。
Case 2:Cost First
对于性能要求不严苛,但是成本比较敏感的场景,可利用索引量化或磁盘+内存的方式,在有限内存里装更多的数据,代价就是牺牲召回率或性能。索引的量化包括SCANN,IVF_SQ8,以及Milvus 2.5推出的HNSW_SQ/PQ/PRQ索引;而使用磁盘的方式,一是可以使用磁盘索引,创建Index时选择DISKANN类型。第二种则是在配置文件中开启mmap:
如果对于成本非常敏感,对于性能没有要求,推荐将 vectorField、vectorIndex、scalarField、scalarIndex 的 mmap 开关都打开。如果查询过程中需要用到标量字段且希望标量过滤性能足够快,可以将 vectorIndex、scalarIndex 的 mmap 开关关掉,来加速标量搜索的性能。配合mmap使用的常见索引是HNSW,索引会比原始数据膨胀1.8倍左右。挂载100G的磁盘,实际能装的数据只有50G左右,如果将原始数据也缓存到磁盘上,则实际能装的数据就会更少,需要提前规划好磁盘空间。
安装
集群
客户端
Java
官方提供的客户端:milvus-sdk-java,第三方客户端spring-boot-starter-milvus。
GUI
zilliz推出的Attu,没有其他推荐的,也无需考虑。