学习型索引在数据库中的应用实践

9月29日,我们邀请到开务数据库研发工程师邹彤老师与大家一起研读大咖论文,主题为《学习型索引在数据库中的应用实践》

索引是数据库引擎的重要组成部分,在当下数据井喷式爆发的阶段,如何高效准确地在海量数据中快速检索某条或某个特定范围的数据就显得尤为关键。

通用的数据库系统为不同的应用需求与数据类型提供了统一的处理方式,在取得了巨大成功的同时,也暴露出一定的局限性:由于没有结合具体应用的数据分布与工作负载,系统往往难以保证性能的最优。

为了解决这一问题,“学习式数据库系统”成为了目前数据库领域的研究热点,它利用机器学习技术有效捕获负载与数据的特性,从而对数据库系统进行优化。

学习式数据库系统当中,出现了一个对数据库索引结构产生颠覆性影响的领域—学习型索引结构 Learned Index。

论文重点回顾

01

2018 年 Jeff Dean 等人在 SIGMOD 发表了《The Case for Learned Index Structures》,成为 Learned Index 的开山之作。近年来,SIGMOD 每年都有一定数目的论文聚焦此领域,论证学习型索引在各种存储结构中的合理性和可行性,也逐渐拓宽了学习型索引的适用场景,支持增删改操作、多维索引、数据倾斜负载等。

索引是实现数据高效访问的重要途径,有助于快速得到键的相关信息,如地址、存在与否等。学习型索引使用机器学习中的回归模型建立起键值与数据之间的对应关系,或建立分类模型判断键值是否存在,从而利用数据分布或特征对索引结构进行优化,使索引变得专用。

在 《The Case for Learned Index Structures》论文中,我们解读了机器学习模型如何与传统的 B 树、布隆过滤器等结构结合,优化索引结构。该文章的基本观点:索引就是模型。

Range Index(以 B-Tree 为代表)可以看做是从给定 key 到一个排序数组 position 的预测过程;Point Index(以 Hash 为代表)可以看做是从给定 key 到一个未排序数组的 position 的预测过程;Existence Index(Bloom Filter)可以看做是预测一个给定 key 是否存在。因此,索引系统是可以用机器学习模型去替换的。

索引一般是通用结构,对数据分布不做任何假设,也没有利用应用负载中真实数据的模式。描述数据分布的连续函数,可以用来构建更有效的数据结构或算法(机器学习模型)。假设用于 Range Index 的 key 的范围是 1-100M,那么可以用特殊的函数或者模型(直接把 key 本身当成是 offset)做索引而不必再用 B-Tree。

以 B-Tree 为例,它可以被看做 Regression Tree。B-Tree 的建立过程也是依赖数据的,只不过它不是通过梯度下降得到,而是依赖预先定义的法则。查询时给定一个 key,B-Tree 会索引到包含该 key 的对应范围的叶子节点,在叶子节点内对 key 进行搜索。

如果该 key 在索引中存在,就会得到其对应的 position(position 为指向逻辑页的一个 pointer)。出于效率考虑,一般在一个逻辑页内的 records 会用首 key 来 index。如图,输入一个 key,输出是对应要查询 record 的 position 的区间 [pos, pos + pagesize]。

仿照 B-Tree 建立模型,输入是键值和数据位置构成的元组 (key, pos),模型将预测键值的位置 pospred,输出为 [pos - min_err, pos + max_err],其中 min_err 和 max_err 分别为每一组 pos 和 pospred 的正负差中的最小负差和最大正差。如果 key 存在,就可以在 [pos - min_err, pos + max_err]内通过二分搜索查找到。而此处所用的模型为数据的累积分布函数(Cumulative Distribution Function ,简称 CDF)。

学习型索引在数据库中的应用实践_第1张图片

为了控制误差,解决 Last Mile Accuracy 问题,建立 RMI(Recursive-Model Indexes)。RMI 主要利用了分段函数的思想,举个例子:要通过单一模型预测 100M 个 key 的位置产生的最小和最大误差是很大的。但如果采用两层模型,第一层模型将 100M 个 key 映射到 10k 个位置上,第二层将 10k 个 key 映射到 1 个位置上,则最小和最大误差会降低很多。

学习型索引在数据库中的应用实践_第2张图片

RMI 的基本结构是树,一般 2~3 层。根结点和中间结点的模型起引导作用,一直引导输入 k 到叶结点。叶结点模型根据其输入得出地址。RMI 的层数的递增表示数据范围的缩小,直至叶结点可以拟合最小范围的数据分布。

B-Tree 的复杂度为 O (log N) ;如果考虑数据分布,ML 会得出类似于 Pos=20 * key (id) 的线性模型,复杂度直接变成了 O (1)。同时在测试集上证明了 Learned Index 比 B-Tree 查找更快,索引 Size 更小。

但是目前依然存在3点局限性:
(1)仅支持只读场景;
(2)线性模型不适用更复杂的数据分布;
(3)不支持多维索引。
这些问题将在后续系列论文中提出解决方案。

02

首篇 Learned Index 只支持只读的 Workload,给现实环境中部署 Learned Index 带来挑战。《ALEX: An Updatable Adaptive Learned Index》解决了这个难题。ALEX 是一种可更新的内存性索引,它结合 Learned Index 的关键洞察以及已经证实的存储与索引技术,在动态的 Workload 上实现了高性能和低存储空间占用。接下来我们将按照下图逐一讲解知识要点:

学习型索引在数据库中的应用实践_第3张图片

数据节点布局—数据节点存储以下内容:
(1)线性回归模型;
(2)存储 key 和 payload 的 Gapped Array;
(3)Bitmap。

内部节点布局—内部节点存储以下内容:
(1)线性回归模型;
(2)包含子节点指针的指针数组。

相比于 Learned Index,ALEX 的内部节点能够更灵活的划分 key 空间。按照分布将线性空间分配给数据节点,非线性空间分配给内部节点。

查找算法:从根节点开始,使用模型向下遍历树,直到达到数据节点。在数据节点利用模型,并结合指数搜索,找到实际位置。

插入到未满节点:ALEX 采用一种 model-based 插入方式,通过模型预测新元素的插入位置,确保模型的准确性。

插入到已满节点:
(1)节点扩展机制:将包含 n 个 key 的数据节点扩展为长度为更长的 Gapped Array,对模型进行缩放或重训练,再利用该模型进行 model-base 插入;
(2)节点分裂机制:节点分裂会将数据节点划分为两个,平均划分 key space,并为每个新节点分配指针。有两种分裂方法:水平分裂和垂直分裂。

Cost model:当插入到一个已满节点中时,ALEX 利用 2 类 cost model 决定插入机制:
(1)节点内 cost model;
(2)遍历到叶子结点的 cost model。

插入算法—结合 cost model 和插入机制,插入算法如下:
(1)一个节点在初始创建时,根据简单假设,利用节点内 cost model,计算 expected cost;
(2)结合实际统计信息,利用节点 cost model,计算 empirical cost;
(3)根据上述 cost 选择插入机制。

优势与挑战:
(1)优势:相比于之前的 Learned Index和 B-Tree,查找时间更快,模型更小;支持更新。
(2)挑战:多并发;多数据类型;离群值影响;参数调优等。

03

以上均以 B-Tree 为主阐述了 Learned Index 的实现,RadixSpline 的出现使得 Learned Index 在 LSM Tree 存储结构中成为可能。接下来让我们一起回顾《RadixSpline: A Single-pass Learned Index》

RS 索引的组成:Spline points 样条点和 Radix table。样条点是 key 的子集,经过选择后可以对任何查找键进行样条插值,从而在预设的误差范围内得出预测的查找位置。Radix table 有助于为给定的查找 key 快速定位正确的样条点。

在查找时,Radix table 用于确定要检查的样条点的范围。搜索这些样条点,直到找到围绕 key 的两个样条点。然后,使用线性插值来预测查找 key 在基础数据中的位置(索引)。因为样条插值是误差有界的,所以只需要搜索(小)范围的数据。

构建 Spline:构建模型 S (ki) = pi ± e,一种映射关系。

构建 Radix table:类似基树/trie,一个 uint32_t 数组,它将固定长度的 key 前缀(“radix bits”)映射到带有该前缀的第一个样条点。key 的前缀是基表中的偏移量,而样条点被表示为存储在基表中的 uint32_t 值(图中指针)。

学习型索引在数据库中的应用实践_第4张图片

首先分配一个长度为的数组,然后遍历所有样条点,每当遇到一个新的 r 位前缀 b,就将样条点的偏移量( uint32_t值 )插入基表中偏移量 b 处的槽中。由于样条点是有序的,基数表是从左到右连续填充的。

Single Pass:构建 CDF、Spline 和基表都可以在运行中进行,只需一次遍历已排序的数据点。当遇到新的 CDF 点时(即 key 改变时),将该点传递给 Spline 构造算法。在同一过程中填充预先分配的基表也很简单:每当在选定的 Spline 点遇到新的 r 位前缀时,就在表中新增一条记录。

查找过程:首先提取查找 key 的 r 位前缀 b(例中为 101)。然后,使用提取的位 b 对基表进行偏移访问,检索存储在位置 b 和 b +1(图中 5 和 6)的两个指针。这些指针在样条点上缩小了搜索范围。用二分搜索查找键周围的两个样条点,在这两个点之间进行线性插值,获得 key 的估计位置 p。最后,在误差范围内进行一次二分搜索法运算,以找到第一个出现的键。

三篇论文总结

本次 Paper Reading 的系列文章论证了学习型索引在不同索引结构中的合理性和可行性,利用工作负载的数据分布,对数据键值进行拟合,构建键值和位置的映射关系。

在 ALEX 中,学习型索引在支持写入操作的同时,查询更快,索引占用空间更小。RadixSpline 做到了在 LSM Tree 存储结构中一次性构建索引,大大缩短了索引构建时间。然而,这些方法仍存在机器学习模型的 3 大通病:

(1)由于估计存在误差,很难保证语义正确性。数据库的索引通常需要满足严格的限制条件,例如 Bloom Filter 的优势之一就是不会把存在的判断为不存在(False Negative),但学习型索引就无法完全保证这一点;

(2)普适性问题。如果新数据出现了数据偏移,将不再符合原来的模型;另一种情况是,数据服从其他的分布模式,或者数据无法通过分布描述,则很难对其构建模型;

(3)Inference 代价问题。模型完成后,新数据的加载模型 Inference 的代价一般要比显式的索引数据结构的计算昂贵。

综上所述,如果要利用工作负载的数据分布,则需要在线模拟一个工作负载样本,并在样本数据上做增量拟合或训练;在学习型索引构建方面,可以聚焦 LSM Tree 的某个阶段,例如 Compaction 阶段;另外,最新的论文《From Wisckey to Bourbon:A Learned Index for Log-Structured Merge Trees》中提到了如何在 LevelDB 中构建索引,并且加速 SSTable 的查找。未来,我们也将继续关注并探索在开务数据库中更多的应用可能。

你可能感兴趣的:(索引数据库)