列式数据库专栏——和磁盘趋势一样,CPU 趋势将有利于倾向采用列式存储

 
一个由多名专家撰稿的关于数据库技术和创新的博客。
和磁盘趋势一样, CPU 趋势将有利于倾向采用列式存储

我们讨论了海量存储技术趋势倾向于使用主要为决策支持查询的数据库系统中采用的列式存储体系结构。在本贴中,Sam Madden 和我思考为什么 CPU 趋势会对数据库设计的选择产生类似的影响。


行式存储中的片式页会降低 CPU 性能

大多数行式存储体系结构使用“片式页”概念,将记录保存在磁盘页上。除了一些控制信息之外,可将片式页大概分为两个主要区域,如下图所示。

 

第一个区域一般从该页的前面开始,它用来将记录在页面上紧密排列地存储。第二个区域,也叫作“分片数组”,从该页的底部开始,向前面伸展。该页上的每个记录在该数组中都有一个条目。每个条目包括相应记录从该页开始处的偏移量和记录的长度。

利用这个布局方案,通过一个由文件或卷标识符、文件 / 卷内页号和分片号的组合来标记记录。这种通常称为 TID (即元组 ID )或 RID (即记录 ID )的地址提供了一定程度的间接性,这在以后变得非常重要。具体来说 可以将记录放在数据页的不同位置上 但保留相同的分片 ID 。这就意味着可以进行插入、删除和更新操作,而不会影响包含指向该页中的记录的条目的索引。

现在考虑一下,当对记录文件应用选择谓词时会发生什么情况。会用到两个迭代器。第一个迭代器在文件的所有页中循环。对于由该迭代器返回的每一页,封装了页面逻辑的第二个迭代器将进行初始化,然后对页中的每一个记录都调用一次。每次调用时,它就会前进到分片数组的“下一个”位置,以获取该页上相关记录的偏移量。
一旦获取了记录开始的偏移量,数据库系统的记录处理逻辑将计算正在应用谓词的属性偏移量。然后将这两个偏移量添加到内存中该页开始的地址里,以获得属性地址。只要计算出这个地址,谓词就得到了应用。

这一过程中,至少对每个记录执行了两次内存访问 一次是访问分片数组条目,一次是访问要执行选择谓词的属性。对于当今的 CPU 而言,决定应用程序能运行多快的一个关键因素是内存在 L2( 数据 ) 高速缓存中访问结果未命中 ( 即结果尚不在内存中 ) 的频率。
这类未命中通常会造成当今的 CPU 暂停运行 100 个周期(等待数据从主存传输到高速缓存中),在这种情况下,数据库系统会显示其运行速度仅为百兆赫 而非 CPU 的千兆赫速度。

当今 CUP 的高速缓存行通常为 64 字节 (Intel Core 2 Duo) 128 字节Intel Xeon 提供 L2 L3 高速缓存。假设计算机的高速缓存行为 64 字节。假设高速缓存器是“冷的”-即,我们最近没有访问过当前页分片数组条目占4 字节(偏移量和长度每个占 2 字节)将造成每 16 次访问分片数组都会出现一次 L2 高速缓存未命中,导致将其它 64 字节内存读入该高速缓存中。另一方面,每个属性访问将造成一次 L2 高速缓存未命中当然,除非记录的长度等于或少于 32 字节,在这种情况下,每个 L2 高速缓存未命中将把两条记录从内存拖到高速缓存中。如果空值是采用一个在记录前的比特数组来实现的,情况可能会更遭,因为可能需要额外内存访问来访问比特数组,以确认属性是否为空。这种访问将造成 L2 数据高速缓存未命中。如果属性为非空,将引起另一个未命中,除非属性和空比特数组都在同一 64 字节高速缓存行里。

为更具体说明这个问题,那就假设页大小为 32 KB,记录为 200 字节。每页将保留大约 160 条记录。扫描每页将引起至少 170 L2 高速缓存未命中(10 次是扫描分片数组,160 次是扫描记录本身)来处理这 160 条记录。现代的处理器采用了“预取”功能,其能检测出高速缓存行内连续的内存访问,并在引用该缓存行之前自动开始加载下一个高速缓存行。但是,这仅可能在访问分片数组(按顺序遍历)时才有帮助,而以下从分片数组到记录的指针引起了对整页的随机访问,这是预取机制所无法预测的。


列式存储避免了片式页中的 CPU 停滞

现在考虑一下,用列式存储法将表中的每一列都单独储存在一个文件里会发生什么情况。现在为了简化讨论,假设没有使用压缩,而且谓词正在应用的属性为一个 4 字节整数。我们假设列式存储通过将它们写入一个单独的写优化存储中来处理更新、插入和删除操作,并定期将它们归并到主要的数据存储。这个归并过程改写了主存储以及任何关联索引。这意味着,主数据存储中的属性值将逐个“紧密排列”,而且主数据存储里也不需要独立于页位置的记录标识符。因此,列式存储不需要主存储里的分片数组。

考虑一下扫描每页上的值以应用选择谓词的过程。利用 64 字节高速缓存行和 4 字节整数属性值 16 次访问属性将引起一次 L2 数据高速缓存未命中。与行存储引起的 170 次未命中相比,处理 160 个属性将只引起 10 次未命中再次假设,没有高速缓存预取。预取可能对列存储比对行存储更加有用,因为现在都是按顺序访问数据值,而不是根据以下片式页数组的指针产生的随机访问模式来进行访问。

实际上,高速缓存行越大,性能差异就越大。例如,利用 128 字节高速缓存行,每处理 160 条记录,行存储将出现 165 次未命中,而列存储将只出现 5 次未命中。

正如我们在上一个贴子所说,列式存储具有高度的可压缩性。假设对一列整数属性使用 RLE 压缩而且对列的压缩产生了压缩因子 10。每个压缩的列条目将包括一个三维值(值、位置、计数),占 10 个字节。利用 64 字节高速缓存行,每次未命中会将6 RLE 三维值引入高速缓存行。因为 160 条记录将平均压缩为 16 RLE 三维值,所以处理 160 条记录将仅引起 3 次高速缓存未命中。与没有进行压缩的列存储相比,未命中减少了 3 倍以上,而与没有进行压缩的行存储相比,减少了 30 倍以上。


PAX
是行式存储的一个选项但还没得到利用

为了符合广告真实性”,有另一种方法来在称为 PAX (Partition Attributes Across) 的磁盘页上分配行记录存储。由 Ailamaki DeWitt 共同开发的 PAX 将每个数据页分割成为 n 个更小的页,每页用来存储记录的 n 个属性(请阅读 2001 VLDB 会议发表的论文Weaving Relations for Cache Performance(为缓存性能编织关系)》,请单击此处)。由于每个记录都已插入到每页中,所以其每个属性都将保存在相应的小页面中。整体的效果就是一个由 n 列组成的数据页。而这样的设计从本质上有着与列式存储一样的高速缓存行为,并有与传统方式组织的使用片式页的行存储一样的 I/O 性能。据我们所知,还没有一个数据库供应商在其产品中采用 PAX


比较行式存储和列式存储的影响

比较行式存储与列式存储的体系结构将意味着什么类型的性能差异呢?假设对于每个属性的内存访问,我们花费 500 CPU 周期来“计算”(这与近期数据库论文的估算相一致,比如上文提到的 PAX 论文)。如果内存时延为 50 ns,那么一个 L2 高速缓存未命中将消耗 3 GHz 处理器大约 300 周期。在 Core 2 Duo,访问 L2 高速缓存中的记录需要 14 个周期。在非面向行的数据库中,利用 64 字节缓存行、200 字节的空头记录、以及 4 字节分片数组,内存延迟按每个记录增加大约 660 周期(共 1160 周期)即,每16 L2中的 2 + 1 次未命中,加上 3 L2 访问。相反,在列存储中,内存延时按每条记录只增加 45 周期(共 545周期)16 L2中的 1 次未命中加上 2 L2 访问。因此,如果数据库有 CPU 限制这很正常,因为许多生产数据库按每个 CPU 都有足够数量的磁盘来确保这一点列式存储将有比行存储高出两倍以上的记录吞吐量。

总之,列式存储除了比行式存储的压缩能力更强并能节省大量的 I/O之外,更重要的是,它与现代 CPU 的设计有更好的兼容性,并能大大地提高受 CPU 约束的查询的 CPU 吞吐量。因此,随着处理器性能与内存延时的差距变得越来越大,列式存储将继续比行式存储表现得更好。此外,我们认为列式存储还能够更好地利用将来带有数十至数百个内核的 CPU,不过我们将留在未来的贴子中讨论这方面的内容。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值