对于诸如图片、视频、音频等非结构化数据,传统数据库方式无法进行处理。目前,通用的技术是把非结构化数据通过一系列 embedding 模型将它变成向量化表示,然后将它们存储到数据库或者特定格式里。在搜索过程中,通过相同的一个模型把查询项转化成对应的向量,并进行一个近似度的匹配就可以实现对非结构化数据的查询。
在技术原理层面,向量检索主要是做一个 K Nearest Neighbors (K最近邻,简称 KNN) 计算,目标是在N个D维的向量的库中找最相似的k个结果。
在数据量较大场景,KNN 计算通常代价比较大,很难在较短时间内返回结果,此外,在很多场景,用户并不需要绝对精确的相似结果。因此,在真正在使用向量检索时,通常会使用相似最近邻搜索,即 ANN 的方式来替代 KNN,从 k 个绝对最近似结果变成 K 个近似最优结果,以牺牲一定准确度的前提,得到更短的响应时间。
第三种是 Cluster-based,也称为 IVF(Inverted File),把向量先进行聚类处理,检索时首先计算出最近的 k 个聚类中心,再在这些聚类中心中计算出最近的 k 个向量。这种索引的优点是构建速度快,因为构建时只需要多一个 training 的过程。相比于其他常用索引(主要是 Graph-based 索引),只需要额外存储倒排表和聚类中心结构,所以内存额外占用比较少。但也存在相应的缺点,由于每次查询要把聚类中心里面所有的向量都遍历一遍,所以它的查询速度受维度信息影响较大且高精度查询计算量比较大,计算开销大。这类索引通常还会结合一些量化算法来使用,包括 SQ、PQ等。
首先,一个向量数据库需要具备向量类型数据和向量索引的存储与管理相关功能,包括增删改查等数据维护功能,另外,对于向量检索性能通常要求比较高。其次,向量检索通常需要与属性过滤等操作结合计算。最后,向量检索通常会与其他属性结合查询,比如以图搜图等场景,最终需要的,是相似的图片路径或文件。
构建向量数据库时,一种思路是以向量为中心,从底向上构建一个专用的向量数据库,这样的特点是,可以针对向量检索做特定的优化,能够保证较高的性能,缺点为缺乏复杂的数据管理和查询能力,通常需要结合其他数据库来使用。
另一种设计思路是基于现有的数据库和数据引擎增加向量检索相关扩展功能。优势是可以做到 all in one 的数据管理和查询支持,缺点为受现有架构的限制,很难做到较高的检索性能。
添加了专用的 Vector Index 管理模块,包含 向量检索库、向量检索执行器、缓存管理、元数据管理等组件。
存储层添加 Vector Index 相关读写支持,每个 data part 维护一个 Vector Index 持久化文件。
基本使用方式
实际使用时,在建表时可以加一个 Index 的定义,包含索引名称、向量列、以及索引类型信息。
数据导入支持多种方式,比如基于 Kafka 的实时导入,insert file,python SDK 等。
基本查询是一个定式:select 需要的列信息,增加一个 order by + limit 的指令。查询支持与标量信息结合的混合查询,以及针对 distance 的 range 查询。
遇到的挑战
在添加高性能向量检索功能过程中,ByteHouse 主要克服以下三大难点:
读放大问题
根本原因:ByteHouse 中,当前最小的读取单元是一个 mark,即便通过 Vector Index 查询得到结果是有行号信息的,但是在真正读取的时候仍需要转成对应的 mark id 传给下层存储层读取。
优化:
1.把向量检索的计算进行了前置处理。
最初的设计中,向量检索计算时需要每个 data part 首先做 Vector Search 加上其他列信息的读取,然后再去做后面的 order by + limit 得到最终的结果。这种做法相当于每个data part 要取 top k,它的读取的行数是 part 数量乘以 mark_size 乘以 top k。这里做的优化是将 Vector Search 计算前置,上推到 data part 的读取之前,首先执行所有 data part 的 Vector Search,获取全局的 topK 个结果,再分配到各个 data part 去做 read。这样可以实现 IO 从百万减到千的级别的降低,实际使用中整体性能实现了两倍以上的提升。
2.存储层的过滤。
把 row level 的查询结果往下推到存储层读 mark 的位置进行一些过滤,减少了反序列化的开销。
3.在 filter by range 场景进行优化。
基于主键查找如按天查找或者按 label 查找等场景,只对首尾 mark 进行了一个读取和过滤,降低过滤语句的执行开销。