Preassumption
-
本文默认读者大概知道一些简单的数据库知识(索引、Page概念等)和 SQL 知识。
-
本文默认读者大概知道 PostgreSQL 是什么。
-
本文默认读者大概对 AI 与向量表达之间至少有模糊的认识:向量是AI中常用的数据表示形式之一,它可以用来表示各种类型的数据,如图像、文本、音频等。在AI中,向量可以表示为一组数字,每个数字代表向量在不同维度上的取值。通过对向量进行数学运算和处理,AI可以从数据中提取特征、进行分类、聚类、回归等任务。向量的表示和处理是AI算法和模型的基础,它们帮助AI系统从数学意义上理解和处理复杂的数据,从而实现各种智能任务。
-
本文主要是面向数据库视角,对 AI 算法部分只表述其大致思想和在性能、准确度上的优劣等。
Faiss From Meta for similarity search
Faiss: A library for efficient similarity search
Faiss is a library for efficient **similarity search **and clustering of dense vectors. It contains algorithms that search in sets of vectors of any size, **up to ones that possibly do not fit in **RAM . It also contains supporting code for evaluation and parameter tuning . Faiss is written in C++ with complete wrappers for Python/numpy. Some of the most useful algorithms are implemented on the GPU . It is developed primarily at Meta's Fundamental AI Research group.
Facebook Faiss(Facebook AI Similarity Search)是Facebook开发的一个用于高效相似性搜索的库。它是一个针对大规模向量数据集的特定索引库,可以用于快速查找最相似的向量。
Faiss旨在解决大规模向量检索的问题,如图像和文本搜索、聚类、近似最近邻搜索等。它提供了一系列高效的索引结构和相似性度量算法,使得在大规模数据集上进行快速的相似性搜索成为可能。
Faiss的主要特点和功能包括:
-
高效的索引结构:Faiss支持多种索引结构,包括倒排文件(Inverted File)、Product Quantization、HNSW(Hierarchical Navigable Small World)等。这些索引结构经过优化,可以在内存或磁盘上快速存储和检索向量数据。
-
多种相似性度量算法:Faiss支持多种相似性度量算法,如欧氏距离、内积、余弦相似度等。用户可以根据具体应用选择适合的相似性度量方法。
-
大规模数据支持:Faiss专注于处理大规模数据集,能够有效地处理数百万或数十亿个向量。它通过将索引划分为多个分区,实现了高度可扩展性。
-
GPU加速:Faiss还提供了对GPU的支持,可以利用GPU加速相似性搜索,进一步提高搜索效率。
Faiss在实际应用中被广泛使用,特别是在图像和文本相关的领域。它在推荐系统、图像搜索、聚类分析等场景下具有重要作用,可以大幅提升搜索速度和准确性。Faiss 作为开源的支持可持久化的且由 Meta 背书的 Library,被很多需要简单相似度计算的数据库、计算引擎等作为向量能力集成的底层支持。
PASE: PostgreSQL Ultra-High-Dimensional Approximate Nearest Neighbor Search Extension (SIGMOD'20)
PASE 是蚂蚁集团搜索团队发表于 SIGMOD'20 的成果,大体就是 PostgreSQL + Vector 的形态。
ABSTRACT
Similarity Search 在各个领域中被广泛使用,尤其是在阿里巴巴的生态系统中。开放源代码的向量相似度搜索解决方案只能支持单个向量的查询,而现实生活中的场景通常需要处理复合查询。此外,现有的开源实现只提供 Runtime Library(即不是个完整的架构方案),难以满足工业应用的需求。为了解决这些问题,我们设计了一种扩展PostgreSQL(PG)索引类型的新方案,从而实现了向量相似度搜索。本文提出了两种代表性的最近邻搜索(NNS)算法。这些算法实现了高性能,并提供了诸如支持复合查询和与现有业务数据无缝集成等优点。在提出的框架下,其他 NNS 算法也可以很容易地实现。实验在大型数据集上进行,以说明所提出的检索机制的效率。
Introduction
Meta FAISS 或 MS SPTAG 等开源相似度搜索库有如下问题:
-
现实场景中通常不止是需要一个向量相似度搜索,而是需要复合查询,即包括向量和普通字段筛选的搜索。单纯的向量搜索库无法支持复合查询。
-
作为偏 research 的工具库,工业级用户使用的时候需要花费心思去完善其稳定性、高可用等方面。
-
现实世界中很多商业数据难以迁移。(更像是企业内部技术决策问题而非技术问题,做到 PG 里一样也有可能要迁移。)
选 PG 的原因就是因为综合拓展能力、稳定性与成熟度、用户接受度等各方面来说,几乎是基于现有成熟偏 TP 的数据库去拓展这条思路的唯一选择。PG 社区拓展很多,PG 也有一些类似的向量相关拓展:
-
ImgSmlr。支持图像相似度搜索,但向量维度只能到 16,且按照原作者话来说,更像是个 POC 验证。("The official statement from the developer also claims that the purpose of ImgSmlr is to provide index design ideas rather than large-scale industrial applications. ")
-
Cube based on GiST index. 支持维度可高达100- ,支持各种向量操作,但是性能低。
-
Freddy with ANN algo. 通过拓展 SQL 能力去支持相似度搜索,但是无法和优化器、索引等数据库优势结合,性能低。
总结基于 PG 去定制拓展有两种思路:
-
Freddy 这类的外接引擎然后拓展 SQL 能力去实现的方案。耦合低、工作量相对少,但性能生产环境不可接受。(可以粗略理解为PG 原引擎查出数据,Freddy 通过向量搜索查出的主键,然后再内部做 JOIN 等,有点像多源查询然后拼到一起)
-
PG 新增一种向量相关的索引类型。但目前已有的方案都或多或少有向量维度支持不够、性能低、搜索准确度低等问题。
因此,本文的主要贡献是提供了一种较通用和灵活的 PG 集成高维向量查询的能力。
Related background
PG Index related Introduction
www.postgresql.org/docs/curren...
-
Page:PG 的数据组织基本单位,通常为4KB 的整数倍对齐(i.e. default 8KB)。
-
PageHeader:页头,存储关于该页的元信息。
-
PageTailer :页尾会存储指向下一个页的跳转信息,方便做scan。
-
IndexAmRoutine:PG 提供的自定义索引适配层,向上封装统一接口。
ANN Algorithm
ANN 大致可分为下列几种类型的算法:
-
tree-based: KD-Tree, RTree, etc. 基于树的算法将数据空间分层,并使用探索树进行搜索。这种类型的算法对低维向量很有效,但在高维数据空间中性能下降明显。
-
quantization-based: IVFFlat, IVFADC, IMI, HC. 基于量化的算法将向量数据分组到几个聚类中,并且查询向量预计在更接近它的某些聚类中,从而避免了暴力搜索。该算法具有简单、精度高的特点。然而,当数据集太大时,搜索效率会降低。
-
graph-based: HNSW, NSG, SSG. 基于图的算法采用邻域图来组织数据,并通过对最近邻居的贪婪扫描来找到相似的向量。即使在大型数据集上,它也表现得非常好。相比之下,它在构建邻居图上花费的时间相对较长。此外,当数据集太大时,图存储是另一个瓶颈。
-
hash-absed: LSH(局部敏感哈希). 基于哈希的算法旨在通过超平面和哈希向量数据切割高维数据空间,通过直接哈希搜索加快计算速度。虽然该算法简单高效,易于实现,节省存储空间,但与其他算法相比,精度较低。
PASE 综合所面对的数据场景考虑,排除了高维场景性能差的 tree-based 和简单粗暴精确度低的 LSH,选择实现 IVFFlat 和 HNSW。
-
IVFFlat:IVFADC 的简化实现版本。对比其他 quantization-based 来说,实现简单直接,构建索引快,存储占用也相对少一些。在高准确率和高实时性上是一个相对兼顾的比较好的quantization-based算法。且算法可解释性较强,甚至支持使用算法的用户定制化聚类方法。综合来说是工业级应用比较好的选择。
-
HNSW:Hierarchical navigable small world is more suitable for a larger dataset (over 10 million entries) with a strict latency requirement (10 ms). 相对来说,在搜索相似度时,HNSW 可以收敛在有限和相比更少的迭代中找到较好的答案。但是 HNSW 有着一些图算法的通病:需要维护较多边点信息在内存里、对存储要求也较高、算法内部复杂较难快速调优。
Implementation of PASE
-
index storage layer: PASE 提供了单页、页组织、连续存储、跨页存储的能力,简单说就是基于PG 的 page format 设计了新的 page 及其组织。
-
index function layer: PASE 基于 IndexAmRoutine 实现了
IVFFlat Index Page
对于高维向量,可以借助聚类算法(比如机器学习里常见的 KMeans 算法)去对向量做个预聚类。步骤如下:
-
假设有个查询向量,利用聚类算法找出和它最近似的 N 个cluster(在某些聚类算法里可以理解为计算与 cluster 中心节点 centroid 的向量距离)。
-
遍历这 N 个 cluster 的所有向量(brute-force 暴力搜索),计算查询向量与被遍历向量的近似度。
- IVFADC 在这一步会有不同,通过乘积量化 product quantization 避免暴力搜索。
-
生成出 Top-K 近似的 vector 结果。
在这个过程中,IVFFLAT 有一些调优细节:
-
可以设置阈值控制 N 的大小,N 越大,搜索的 cluster 越多,准确度越高,耗时也越高。
-
一些距离过远的 cluster 也可以提前 prune 掉。
PASE 为 IVFFLAT 引入了三种新的 Page:
-
meta page 用于存储索引的元信息,包括涉及向量的维度、data page 的数目等。
-
centroid page 用于存储 cluster 信息(centroid vector 和归属这个 cluster 的数据页跳转信息),其中内部的存储 Tuple 称之为 CentroidTuple (对应原PG数据页的 DataTuple 概念)
-
data page 是逻辑上以拉链的形式存储,每个 cluster 维护一个 page chain。DataTuple 存储原始向量数据。一个 page 内部的向量都是连续存储的。
从图中可以看出,Tuple 里会存储跳转信息跳转对应 page 的 PageHeader,对应的 PageTailer 存储拉链的下一个页的跳转信息。
HNSW Index Page
HNSW(Hierarchical Navigable Small World)算法是一种用于高效最近邻搜索的算法。它的原理是通过构建一个多层的图结构,将数据集中的数据点连接起来。每个数据点在图中的邻居被选择为其最近的数据点。通过这种方式,可以在搜索过程中快速定位到候选集,然后再进行进一步的精确搜索。HNSW算法通过平衡搜索效率和搜索准确性,实现了高效的最近邻搜索。详细可见 HNSW算法原理。其中,图化的思想是把每个向量看成一个点,在给定要求找 N 个最近邻居的前提下,每个点都可以通过 K近邻算法(KNN) 去和周边的点构建 N 条边(当然可能会有 overlap),最后形成一个图。
data page 代表 Layer0。第一层及往上存储 format 其实都是一样的。以V1为例,其邻居节点分别为V2、V3、V4,对应的NeighborTuples分别为N1、N2、N3,分别指向目的节点V2、V3、V4。例如,N1指向V2。D1 存储 V1的原始向量数据,V1 存储跳转 D1 的信息。
Index Storage Layer: Page Optimizations
Contiguous Pages
动机:提升在外存的向量索引 scan 性能,把原来 page 随机读通过连续存储尽可能变为顺序读。
优化:
-
物理分配 page 时引入 PageBlock,一次分配一个 PageBlock,一个 PageBlock 内可存储连续的 N 个 page (N可配置)。当然 PageBlock 之间不保证一定是连续的。
-
逻辑上连续的 page 是通过上文提到的 chain 的形式拉链的,新建时基本能保证逻辑拉链与物理连续存储一致。
-
PG Vacuum 时,会在新的 PageBlock 上拷贝一次 page chain ,其中会去掉空洞 page。最后用新 chain 替代旧 chain。
连续页的优化更主要是为 IVFFLAT 服务的,因为 IVFFLAT 的暴力遍历阶段相比 HNSW 会需要读更多的 pages。
Cross Pages
单页肯定有存不下数据的情况,需要跨页存储。跨的页也会是连续页,思想是把大 value segment 化,每个 segment 存一部分。
Applications of PASE
-
图片搜索: 蚂蚁金服的一个典型场景是版权数据检索。蚂蚁金服的版权中心拥有大量的版权数据,包括视频、图像、文字等,其中以图像居多。每个材料都有一个嵌入向量作为它的指纹,通过它我们可以利用神经网络来检查是否存在任何类似的情况,并检测版权冲突。该数据集由数十亿个512维向量组成,查询延迟预计小于300 ms。我们已经建立了PASE来支持大规模的向量检索。在这种情况下,指标规模巨大,对准确率的要求不像人脸识别那样严格。我们关注召回指标R1@100(前100个搜索结果中真实项目的比率)而不是R1@1(前1个搜索结果中真实项目的比率)。对于这种情况,HNSW比IVFFlat更合适,因为HNSW可以达到与IVFFlat相同的精度,而计算成本仅为IVFFlat的10%。特别是对于巨大的索引案例,节约计算成本对于整个项目的可行性至关重要。
-
人脸识别: 阿里云为商场客户实时跟踪提供解决方案。利用商场内的相机拍摄顾客的照片,并从面部照片中提取特征向量。通过从视频存储中查询每个客户的图像,我们可以知道每个客户的路由偏好。该方案由阿里云的数据库PolarDB[5]支持,该数据库与PASE集成,实现矢量检索。数据量约为100万,向量维数为512。与上述版权案例不同的是,R1@1预计将高于90%。HNSW也适用于这种情况,我们需要调整一些关键参数以满足精度要求。增加邻域数是提高查全率的有效方法。其他人脸识别场景包括地铁的面部扫描支付。数据量为数十万级,数据维数为256。一般的面部扫描支付要求最严格的召回率,在这种情况下要求高于99%。对于这些具有严格精度的场景,IVFFlat是最佳选择,因为我们只需要改变遍历集群/总集群的速率。在这种情况下,我们设置0.1作为比率,R1@1可以达到高于99%。经评估,在比率> 0.1之后,我们的案例中召回率的增长趋于平缓。
-
个性化推荐:支付宝商品推荐技术从传统的协同过滤升级到基于深度学习的兴趣挖掘。将项目和用户分别嵌入到向量中,利用PASE提供的向量检索能力找到用户最喜欢的项目。一个关键的挑战是我们的系统需要支持数以万计的QPS。同时,查询延迟需要小于10ms。幸运的是,向量维数只有40个,召回率在80%-90%左右。对于中等准确率的场景,我们可以以降低查全准确率为代价获得更快的计算速度。我们尝试减少HNSW的邻居计数和搜索队列(efsearch)的大小,HNSW在这个场景中表现得很好,正如预期的那样。
AnalyticDB-V: A Hybrid Analytical Engine Towards Query Fusion for Structured and Unstructured Data (VLDB'20)
ABSTRACT
随着非结构化数据(如图像、视频和音频)的爆炸性增长,非结构化数据分析在丰富的现实世界应用中得到了广泛的应用。许多数据库系统开始将非结构化数据分析纳入其中,以满足这些需求。然而,大多数系统在处理混合查询(即涉及两种数据类型)时,往往将查询视为不同的任务,其中混合查询尚未得到完全支持。该论文介绍了阿里巴巴开发的一种混合分析引擎,名为 AnalyticDB-V(ADBV),以满足这种新兴的需求。ADBV 提供了一个接口,使用户能够使用 SQL 语义通过将非结构化数据转换为高维向量来表达混合查询。ADBV 采用了 lambda 框架,并利用近似最近邻搜索(ANNS)技术的优点来支持混合数据分析。此外,提出了一种新的 ANNS 算法,以提高表示大量非结构化数据的大规模向量的准确性。所有 ANNS 算法都在 ADBV 中作为物理运算符实现,同时,提出了精度感知的CBO 技术,以确定有效的执行计划。在公共和内部数据集上的实验结果表明,ADBV 实现了优越的性能,并证明了其有效性。ADBV 已经成功部署在阿里云上,为各种现实世界的应用提供混合查询处理服务。
Introduction
ADBV 的混合查询实际就是普通筛选加向量查询一起组合查询(如上图例子)。
提出混合查询动机:
-
单一的向量搜索不能满足阿里内部实际应用场景需求。
-
大多数向量搜索在大数据集下为了实时性会牺牲准确率。增加普通筛选可以有效降低实际向量部分的搜索空间,从而提升性能和准确率。
面临挑战:
-
高维向量的实时读写。在阿里内部很多向量维度可以去到500+,现有解决方案要么性能很差,要么支持不了高维向量。
-
优化器适配混合查询。比如传统优化器做 TopK 的时候,不会考虑向量搜索准确度问题,只会考虑执行代价。而向量的 TopK 可能根据 ANN 搜索算法 (ANNS) 的不同,准确度也会有差异。(这个可能是基于 ADBV 预期会在同样的向量索引提供不同的 ANNS 而派生出来的问题,在比较 hardcode 的实现里这个可能不是问题)
-
高可用与高并发。论文提及在阿里内部面临很多 millions 甚至 billions 的高维向量数据,以盒马为例子数据库查询峰值 qps 4000 的时候 80% 是混合查询。
ADBV Contributions faced with challenges:
- 引入不同的 ANN 算法应对不同场景,neightborhood-based 的算法吃内存但是支持实时写入;encoding-based 的算法消耗内存少、相对吞吐高,但是只能是偏离线的批处理用法。ADBV 引入一个机制来实现实时写增量和周期性后台攒批写全量,这个机制被称为 lambda framework。
-
一个新的 ANNS 算法------Voronoi Graph Product Quantization (VGPQ)。相比传统 IVFPQ,VGPQ 在大数据集下的构建索引和查询性能都更好。
- IVFPQ(Inverted File with Product Quantization)算法是一种用于高效近似最近邻搜索的算法。它的原理是将数据集划分为多个子空间,并使用乘积量化(Product Quantization)对每个子空间进行编码。然后,通过构建倒排文件(Inverted File)来加速搜索过程。在搜索时,通过计算查询向量与倒排文件中的编码向量之间的距离,找到与查询向量最相似的候选集。最后,通过线性扫描候选集来找到最近邻。这种算法在大规模数据集上具有较高的搜索效率和较低的存储开销。
-
Accuracy-awared CBO. ADBV 把 ANNS 算法执行包装成了一个物理算子,选择合适的物理算子是 CBO 的工作。在实时性要求和数据量膨胀的矛盾下,牺牲一定准确度换实时性是比较常见的做法,因为机器学习算法本来就有非精确性的特征。如何用 cost 衡量不同数据量下的计划(i.e. 是选择精确但是耗时耗资源的、还是选择没那么精确的),就是 CBO 考量的问题。
SQL Dialects
一些引入 Vector 之后的 ADBV SQL Examples
SQL
ANN INDEX feature_index (column_feature)
INSERT INTO table_name (C1 , C2 ,· · ·,Cn , column_feature) VALUES
(v1, v2, · · ·,vn, array [e1 ,e2 ,e3 ,... ,ek ]:: float []);
SELECT * FROM table_name WHERE C1 = 'v1'
ORDER BY DISTANCE(table_name.column_feature , FEATURE_EXTRACT('unstructured data'))
LIMIT k;
CREATE TABLE clothes(
id int,
color varchar ,
sleeve_t varchar ,
· · ·,
image_url varchar ,
feature float[512] COLPROPERTIES (
DistanceMeasure = 'SquaredEuclidean',
ExtractFrom = 'CLOTH_FEATURE_EXTRACT(image_url)'
)
) PARTITION BY ClusterBasedPartition( feature );
System Design
-
Coordinator: 接受、解析、优化 SQL 语句,并将它们分配到读/写节点。
-
读写分离结构:trade off consistency for low query latency and high write throughput
-
存算分离:
-
伏羲 Fuxi:阿里内部的底层调度系统
-
Lambda Framework:
现有构建向量索引的算法(树、图、量化、LSH等)在实时性、准确率、资源占用上都无法达到一个平衡最优解。其中能在超大规模数据集上达到实时性的且工业级可用的目前仅有 HNSW 和 LSH,但 HNSW 规模上去后特别消耗内存,LSH 则是准确率较低。因此 ADBV 将写入数据分为了增量和全量两部分。增量数据规模小,采用 HNSW 写入;全量数据规模大,但是后台 Merge 可以对实时性要求比较低,采用定制化的 VGPQ 算法,并在写入成功后丢弃对应 HNSW 增量索引。无论写入还是 Merge 的输入数据来源都是 WAL。
Clustering-based Partitioning
向量不能像结构化数据那样去做哈希或线性顺序的 range 划分,因为向量是基于相似度计算的。因此向量最适合的分区形式就是做聚类。ADBV 提供了个 query hint 参数 N,来给用户去在查询时只选取前 N 个与查询向量最近的 N 个分区去搜索。(其实就是 PASE 的 IVFFLAT 思路)
VGPQ
Voronoid Diagram 沃罗诺伊图(Voronoi Diagram,也称作Dirichlet tessellation,狄利克雷镶嵌 )是怎样的? - 知乎
IVFPQ 中,每个向量会通过量化算法 PQ 进行编码,编码的会把原始高维向量转为compact、low-dim、lossy 的向量表示形式,用于执行时优化计算距离开销。编码后的表示称为 PQ Code。IVFPQ 通过 k-means 将 PQ Code 聚类为组,,输入 query vector 会先也做个分组,然后转发到对应分组做 scan。
VGPQ 基于 IVFPQ 的质心构建 voronoi 图,并根据以下启发式将每个 voronoi 单元划分为几个子单元。为了便于解释,下文用 Ci 表示质心以及对应的多边形。
考虑图 6(a)中的查询向量 q,其 top-3 邻居(由红色圆圈突出显示)应该作为结果返回。由于没有提供任何先验知识,IVFPQ 按照顺序遍历 C0(包含两个目标点)和所有七个相邻单元,以在 C2 中找到第三个目标点。VGPQ 为了解决这个问题,在 C0 和所有相邻质心之间绘制一条线,然后在每个线段的中点处隐式地标记,如图 6(a) 虚线所示。现在,我们可以基于与七个中点的距离将原始单元 anchor centroid C0 划分为七个子单元(可以粗略理解为起个中点作为子单元逻辑质心去对 C0 多边形内的向量分组,这个质心有可能在子单元外,只是个分组依据,并不代表真实存在的向量点)。类似地,所有质心的子单元以相同的方式生成。每个子单元表示为 Bi,j,其中 i, j 表示 anchor centroid Ci 和邻居 centroid Cj。 q 与子单元 Bi,j 的距离表示为 d(q, Bi,j),它被定义为 q 与 Ci 和 Cj 之间的线段中点之间的距离。图中可以看出,q 离 (C0,C2) 与 (C0,C3) 这两条线更近,因此实际上 VGPQ 只需要扫描 6 个子区域 (B0-2, B2-0, B0-3, B3-0, B2-3, B3-2) 。
Storage of VGPQ Index
VGPQ Storage 包括三个部分:
-
Indexing data: 类似 PGSQL index page,但是 index 的层次是 centroid -> subcell 。
-
PQ data: 类似 PGSQL data page,只不过 tuple 存的是 PQ Code。
-
Direct map: 行 ID 和 PQ 代码在 PQ 数据中位置之间的双向映射。给定一个 row ID,直接映射指示 PQ code 在 PQ data 中的存储位置,反之亦然。这主要是一个工程上的优化,映射的存在可以让 PQ code 倒查 row ID 等操作 更简单高效。
Hybrid Query Optimization
给定两个 Query Q1 普通向量查询和 Q2 混合查询(包括他们对应的逻辑计划)作为例子:
SQL
SELECT id, DISTANCE(f, FEATURE_EXTRACT('img')) as distance FROM T
-- return top -k closest tuples
ORDER BY distance LIMIT k;
-- Q1: vector query example
SELECT id, DISTANCE(f, FEATURE_EXTRACT('img')) as distance FROM T
--structured predicates
WHERE T.c >= p1 and T.c <= p2
-- return top -k closest tuples
ORDER BY distance LIMIT k;
-- Q2: hybrid query example
ADBV 首先将这两个查询转换为相应的逻辑执行计划。由于高维向量列 f 中的每个值在存储时通常需要超过 2000B,故减少从磁盘读取的向量操作的数量可以显著提高查询处理效率。因此,ADBV 将关于输入图像的相似性搜索推到存储节点,存储节点只需要吐出查询向量的"邻居"向量,而不需要吐回该列所有值。
在这个 TopK 场景中,ADBV 的优化器分析查询解析器提供的抽象语法树,以检测表示返回 TopK 向量列相关元组(即"order by DISTANCE() LIMIT k")的操作的 pattern。如果找到相应的 pattern,优化器将将逻辑计划转换为多个执行向量列上的最近邻居搜索(NNS)的物理计划。
以 Q2 混合查询为例,ADBV 优化器会对其 Scan 部分生成四种可能的物理计划:
-
Plan A 暴力搜索。按照结构列 c 的条件做 IndexLookedUpScan,然后对 c 筛选后的数据取出 f 列做向量计算,投影出 distance 列,最后算 TopN。这种计划只能适用于行数比较少的场景,但是这种全量计算的方式在DISTANCE 函数符合用户需求的前提下是准确度最高的,至少不会在搜索中丢失精度。
-
Plan B PQ Knn Bitmap Scan。如果类似 1 中筛选后参与向量计算的行数仍比较多(比如 >= 1w),ADBV 就会开始尝试做 NNS 了。Plan B 是这类思路中相对最简单的计划。依然首先对 c 做 IndexScan,然后获取向量列的 column store 存的值,即 PQ Code。PQ Knn Bitmap Scan 算子一个接一个地对每行的 PQ Code 执行与查询向量的 Asymmetric Distance Computation (ADC)。ADC 会算距离(不是精确的),从而得出一个向量的 ADC Top-S,然后往上去做 project 和精确距离的 topn 。这里的思想有点像是用一个查询更快的方法先加速查询出一批可能的结果集 S,然后再对 S 做精确计算。因此 S 期望是要比 TopN 的 N 大的,放大系数被标为可调参数 σ,σ > 1。
-
Plan C VGPQ Knn Bitmap Scan。当行数超过百万时,Plan B 也不能保证在秒级返回结果。此时基于 Plan B 的思想,VGPQ Knn Bitmap Scan 会被纳入考虑。和 Plan B 类似,VGPQ Knn Bitmap Scan 也会通过 VGPQ ANNS 算法产生一个可能结果集 S = σ * N。在 VGPQ 介绍中,有提到 DirectMap 维护了rowid 到 数据存储 offset 的双向关系。因此此时相当于两个 scan 生成了两个结果集,这里会采取 Bitmap Test 的方式去除掉非交集的数据。相比于 Plan B 的 one by one 去用 rowid 计算,先预设 scan S 再做交集显然在大数据集上会更快。但是对应的 σ 的放大含义就更复杂,保证准确性就更困难。同时在 VGPQ Knn Bitmap Scan 场景下的配置参数除了 σ 还有 VGPQ 自身的一些参数。
-
Plan D VGPQ Knn Scan。传统优化器一般要求 Filter 发生在 TopN 算子之前,不然 TopN 的结果可能会不正确。但由于向量搜索天然的非精确性的特征,这个约束实际可以考虑被放开。当一个大表的所有行都要去过结构化列的 Filter 的时候,实际上通常 Filter 计算开销占整个计算的比重也是很大的。因此 Plan D 允许把结构化列的 Filter 提到 向量TopN 之后。首先通过 VGPQ 找出 S = σ * N,然后由于 S 相比原表全表量级基本上是 trivial 的级别(因为通常 LIMIT 都很小,所以 S 一般也不会大到哪去),剩下的执行就很高效了。
可以看到执行计划从 A 到 D,随着数量级增加,ADBV 一步步地牺牲一定的准确度来换取实时性。
Accuracy-aware Cost Model
Plan A 从 cost=T0 的结构化索引扫描开始,并假设有 α × n 条记录被认为是合格的,然后通过 DISTANCE 函数计算查询向量与合格向量之间的相似性。其 CostModel 可表达为:costA = T0 + α × n × c1 。
Plan B 首先执行结构化索引扫描,然后分别估计查询向量与每个向量的 PQ code 之间的近似平方距离的 ADC。这一步找到 σB × k 条记录,以缩小暴力搜索的范围。costB = T0 + α × n × c2 + σB × k × c1
Plan C VGPQ Knn Bitmap Scan 在结构化列的 IndexScan 后执行。costC = T0 + β × n × α × c2 + σC × k × c1
Plan D 将 Filter 和向量 Scan 的执行顺序与计划 C 相反。谓词过滤器的输入大小显着减少,其运行时间变得可以忽略不计。costD = γ × n × c2 + σD × k × c1
给定一个混合查询,可以使用上述公式计算计划执行成本,然后优化器选择成本最小的计划作为最终计划。此外,优化器还需要调整 β、γ 和 σ 的选择,这直接影响查询的准确性。但实际上很难提供一个具有理论保证的公式来推导最优设置。因此,ADBV 使用了一些启发式方法来进行超参数调整。超参数调整过程包括两个步骤,即预处理步骤和执行步骤。
-
在预处理步骤中,**α 的值域被划分为许多不相交的片段,每个片段都被视为一个桶,并通过其 min 值来识别。对于每个计划,使用一种网格搜索方法(论文表示类似于 Faiss 中的自动调谐,可以理解成一种机器学习的调参算法)来枚举所有桶的超参数的所有组合。**在这些组合中,只考虑能够返回满足准确性要求的结果的组合。然后,运行时间最低的组合将被记录为对应计划和桶的最终设置。在 ADBV 中,预处理步骤在 lambda 框架中的批处理层将新摄入的数据合并后进行(即全量 merge 阶段完成后)。
-
在执行步骤中,对于每个 ad-hoc 查询,可以使用复杂的** selectivity estimation 技术来估计α′的值**,这些技术已经在现代数据库系统的查询优化器(i.e. ADB)中存在。ADBV 为每个计划选择属于相应桶的预调谐超参数组合,最后通过计算选定的超参数组合对应的成本模型来确定执行最有效的计划。在实践中,这些组合候选参数对大多数用户查询都很有效。对于某个计划,如果优化器无法从预调谐组合中选择一个实现满意准确性的候选者,它将在以后的决策阶段直接忽略这个计划。在最坏的情况下,当计划 B、C、D 都失败时,优化器仍然可以选择计划 A 进行执行。
Milvus: A Purpose-Built Vector Data Management System (SIGMOD'21)
ABSTRACT
近年来在数据科学和 AI 应用中管理高维向量数据的需求越来越迫切。这种趋势是由非结构化数据和机器学习的普及推动的,其中 ML 模型通常将非结构化数据转换为用于数据分析的特征向量,例如推荐系统。管理向量数据的现有系统和算法有两个限制:它们在处理大规模和动态向量数据时会出现严重的性能问题;它们提供的功能有限,无法满足多功能应用的需求。因此,论文提出并实现了 Milvus, 一种专门用于高效管理大规模向量数据的数据管理系统。Milvus 支持易于使用的应用程序接口(包括 SDK 和 RESTful API);针对现代 CPU 和 GPU 的异构计算平台进行了优化;支持超越简单向量相似性搜索的高级查询处理。
Introduction
Milvus 的公司 Zilliz 的数据科学和AI领域的用户时常面临着大数据规模下高维(10~1000 dim)向量数据管理的挑战。这些挑战包括:
-
Dynamic Data :数据不是静态分析的,同时要支持一定的 INSERT / UPDATE / DELETE 的实时动态修改能力。
-
高级向量查询 (not just single vector similarity search):
Milvus 基于 Faiss 做了性能优化和功能增强,并且增加了分布式支持。
Architecture
-
Coordinator 负责容灾和结点、主从 meta 信息、Load Balance 信息、sharding 信息。Coordinator 是三节点集群,managed by Zookeeper。
-
计算层:一写多读、Stateless in K8S、Hierarchical Cache(Mem+SSD)。
-
底层共享存储可以是本地文件系统、HDFS、S3 等,适配云环境。
-
SDK / Restful API 访问,仅支持如下 QueryType:
Storage
Index
和 PASE ADBV 大家思路都类似,选了 quantizization-based (IVF_FLAT, IVF_SQ8, IVF_PQ) 和 graph-based (HNSW, RNSG) 两大类实现。论文并未提及对相关算法有优化。Index 算法在 Milvus 中是可替换、易拓展的。
Dynamic Data Management
Milvus 基于类 LSM 思想实现写入,新写入会写到内存里的 MemTable,MemTable 会被 size/interval trigger 触发 flush。Compaction 方面,后台参考 Lucene 实现了 Merge 算法。Update & Delete 以非 inplace 的追加写的方式实现。参考 Lucene 的概念,Milvus 数据存储的大单位叫 Segment (可以简单理解成一个 SST),且仅对大于 1GB 的 Segment 构建 inner-segment 的 index。换句话说,没有索引的 segment 查询需要 scan。
Vector Storage
对于单向量实体,Milvus 连续存储所有向量,而不显式存储行 ID。这样,所有向量都按行 ID 排序。给定一个行 ID,Milvus 可以直接访问相应的向量,因为每个向量的长度相同。对于多向量实体,Milvus 以列格式存储不同实体的向量。例如,假设数据库中有三个实体(𝐴,𝐵,和 𝐶),每个实体有两个向量 v1 和 v2,那么所有不同实体的 v1 都存储在一起,所有的 v2 都存储在一起。也就是说,存储格式是{𝐴.v1, 𝐵.v1, 𝐶.v1, 𝐴.v2, 𝐵.v2, 𝐶.v2}。
一句话,向量列存。但是论文并未描述 Delete 等操作造成 rowID 空洞后怎么处理。
Attribute Storage
也是按 column 存储,但是会存 rowID,以 <attr, rowID> key-value pair 形式存储。存储格式如 [<attr1,rowID1>,<attr2, rowID2>...],按 attr 排序。Milvus 提及它有 data page 的概念,对 data page 有构建类似 Snowflake 的 Skip Pointers,即 page 的min、max、count 值等。但并未展开描述 page 相关的大小及设计等细节。
Keypoints / Sparkles
GPU
-
Faiss 原来无法在 GPU 做超过 1024 的 Top-K(受限于共享内存),Milvus 做了优化把一个 1024 batch sort 变成了每 1024 一轮的多轮迭代 Top-K。(从软件层角度看其实是个非常基础的 Top-K inplace sort 优化,但是区别是这个逻辑做到了 GPU 层)
-
支持多 GPU 计算,可以把多 GPU 看成是不相通的 worker 。
-
支持 GPU & CPU co-computation,论文举了 SQ8 算法为例,当算法输入批次大小超过某个阈值时,测试发现算法里有些 IO bound 的部分在 GPU 里比较低效(占用了 GPU 但没产生计算,在等待 IO)。于是可以把 SQ8 拆成两阶段,第一阶段 GPU 计算,第二阶段交回给 CPU。
Attribute Filtering
基本就是参考了 ADBV 混合查询优化的那一部分,但没有 VGPQ,而是增加了一个 partition-based 优化。
-
Stg A:适用于 attribute 过滤比较高的情况。
-
attribute IndexScan
-
GetRow <attr, vector>
-
calculate similarity between query vector and resultset
-
-
Stg B:适用于 attribute 和 vector 的 query 的筛选比都还可以的情况。
-
attribute IndexScan
-
generate rowID bitmap
-
calculate TopN rows of query vector
-
use bitmap scan on TopN rows to get the final TopK
-
-
Stg C:适用于 vector 查询过滤比较高的情况。
-
calculate TopN rows of query vector ,N 是 K 的放大。
-
GetRow <attr, vector>
-
apply attribute filter
-
-
Stg D:CBO,估算 A B C 的代价,选取最优的。
-
Stg E:partition-based. attribute 是数值类型、列存的,独自会有个分区机制,分区数目是由用户建表决定的。分区是按值做 range partition。在 filter 是 range filter 的前提下,partition-based 的策略是:
Multi-vector Query
Each entity contains 𝜇 vectors v0, v1, ..., v𝜇−1 , the similarity of two entities 𝑋 and 𝑌 is computed as 𝑔(𝑓 (𝑋 .v0, 𝑌 .v0), ..., 𝑓 (𝑋 .v𝜇−1, 𝑌 .v𝜇−1)) where 𝑋 .v𝑖 means the vector v𝑖 of the entity 𝑋 . We call the TopK similarity search procedure as multi-vector query.
有一种 Naive 方法是对 entity 的每个 vector 求 TopN,然后合并取 TopK。但显而易见这种算法召回率极低。
在近似度计算方式是做内积的前提下,Milvus 提出一种 vector fusion 的方式。思路就是通过某种聚合方法,把 entity 多个向量表示为一个聚合向量。Milvus 使用的是一种加权函数的方式,论文并未展开关于加权函数的更多细节,且关于加权的向量表示那里用了叉积,疑似与文字描述有不符。
另一种思路是如果近似度计算方式不是内积的话,Milvus 借助 TopK 算法 Fagin's NRA Algorithm 来做 "Iterative merging"。NRA 要求数据按照某种顺序一条条读取,但是 Milvus 现有的向量索引组织形式不支持 真正意义上的 GetNext 这样的接口。Milvus 的改造是迭代 TopK 的 k 值当做 GetNext,相当于第一条就是 Top1,第二条就是计算 Top2 的最后一条,以此类推。Milvus 也承认该方法的性能肯定是不算好的,但是它适用性更广,对近似度函数和数据是否归一化等都没有要求。
Summary on the papers above
-
PASE 对实现细节描述稍微详细点,理论创新点较少,但是作为最早发出论文的类似实践,给了一个比较好的工程样例。
-
ADBV 在混合查询优化上给了一个很好的优化示例,将随着数据量增大如何在实时性和准确度中作 tradeoff 的工程实践描述得很详细,这也是向量查询的一大难题。同时提出了一种进一步 IVFPQ 改良版的算法 VGPQ,优化了inner-cluster 的 brute-force scan。但相对 PASE 来说,略过了一些实现细节,可以理解为是一篇比 PASE 更进阶一点的论文。
-
Milvus 针对场景相对受限,且论文更多是一些工程局部细节的一些代码优化,所涉及的分布式等没有太多理论的创新点。但截止 2023 年,相比论文发表的 2020 年 Milvus 补充了许多新功能,此处请以最新官方消息为准。
总体来说,这三篇论文展示了向量数据库的两种可能实现形式:
-
一种是基于现有数据库引擎(主要是 PGSQL)去定制化改造内核、拓展单独的向量引擎,好处是基本保留了数据库的能力,不足是在一些纯向量的执行场景下性能可能会不如纯向量数据库,同时有可能在工程上受限于原有数据库实现,有些优化难以添加。
-
另一种是从头研发把向量当做一等公民的纯向量数据库,缺点也很明显,数据库能力会相对受限,或数据库的基本能力要重头对标其他数据库竞品去补充。
从数据库视角看,前者在落地和适用场景方面更有优势,是个安全的选择;从 AI infra 角度看,后者可能有机会更贴合纯 AI 需求的场景。