前言
本文详细介绍一下milvus的数据插入,索引构建和数据查询的实现细节
数据插入
在milvus里面,我们可以对每个集合设置多个分片(shard),每个分片都有对应的虚拟通道(vchannel).从下面的图中我们可以看到,milvus给日志代理的每个vchannel都分配了一个物理channel(pchannel)。每个进来的插入/删除请求通过主键的哈希值被路由到对应的分片。
因为milvus没有复杂的事务能力,所以DML请求的有效性验证被前移到了代理。代理需要为每个插入/删除请求从TSO(timestamp oracle)获取一个时间戳,它是与根协调器共存的定时模块.时间戳将会决定数据请求的有序处理,旧的时间戳数据会被新的重写。代理从数据协调者批量检索信息,包括实体的分段和主键,以提高整体吞吐量并避免中心节点负担过重。
DML操作和DDL操作都会被写入日志序列,只是因为DDL操作是低频发生的,所以它仅仅被分配到一个channel里面。
Vchannel维护在底层日志代理节点中。每个channel都是物理隔离的,并且只能被一个节点使用。当数据摄入速率达到瓶颈时,考虑两个事情:日志代理节点是否过载了并需要扩容,是否有足够的分片来为每个节点做负载均衡。
上面的图概述了参与处理写日志序列的四个组件,代理,日志代理,数据节点和对象存储。流程调用了四个任务:验证DML请求的有效性,发布订阅日志序列,将日志流转换成日志快照,持久化日志快照。四个任务都是互相解耦的,以确保每个任务都能被对应的节点类型处理。相同类型的节点都是一样的并可以弹性扩容,以独立地适应各种数据负载、尤其是海量且高度波动的流数据。
索引构建
索引是被索引节点构建的,为了避免在数据更新的时候频繁进行索引构建,milvus里面的集合被进一步划分为段,每个段都有它自己的索引
milvus支持为每个向量字段、标量字段和主键字段构建索引。索引构建的输入和输出都与对象存储有关:索引节点把段(存储在对象存储)的日志快照加载到内存的索引,反序列化对应的数据和元数据来构建索引,当索引构建完成就序列化其写入到对象存储。
索引构建主要涉及向量和矩阵运算,因此需要大量计算和内存。因为向量本身就是高维度的,所以没办法像传统基于树来生成有效的索引,但是可以使用向量搜索当前比较成熟的技术来构建索引,比如聚类或者基于图的索引。无论其类型如何,构建索引都涉及大规模向量的大规模迭代计算,如Kmeans或图遍历。
不像索引标量数据,构建矢量索引必须充分利用SIMD(单指令、多数据)加速。milvus天生支持SIMD指令集,比如SSE,AVX2,AVX512.考虑到向量索引构建时的抖动和资源密集性,从经济角度来看,对milvus来说弹性是非常重要。未来的Milvus版本将进一步探索异构计算和无服务器计算,以降低相关成本。
除此以外,milvus也支持标量过滤和主键查询,它内置了索引来提升查询效率,比如布隆过滤索引,哈希索引,基于树的索引,倒排索引,并且计划引入更多的外部索引,比如位图索引和rough 索引。
数据查询
数据查询是为了找到与给定的目标向量相邻的K个向量或者与目标向量在特定距离范围的所有向量而搜索特定的集合。返回的结果包括向量和相应的主键与字段。
在milvus里面,一个集合被切分成多个段,查询节点根据段来加载索引,当查询请求达到时,它将通知所有的查询节点一样的消息。每个节点精简本地段,搜索符合要求的向量,然后过滤并返回搜索结果。
查询节点在数据查询的时候都是各自独立的。每个节点仅仅负责两个事务:根据查询协调者的指令来加载或释放段;在本地段里面执行搜索。代理负责合并从每个查询节点返回的搜索结果并最终将结果返回给客户端。
有两种类型的段,增量段(存储增量的数据)和 密封段(存储历史数据)。查询节点订阅vchannel来接收最近更新(增量数据)作为增量段。当增量段达到预先定义的阈值,数据协调者密封这些段然后开始索引构建。查询协调者开始初始化移交操作来将增量数据变成历史数据。查询协调者将根据内存使用情况、CPU开销和段的数量在所有查询节点之间均匀分配密封段。