0%

[笔记] Column-Stores vs. Row-Stores: How Different Are They Really?

原文:Column-Stores vs. Row-Stores: How Different Are They Really?

TL;DR

本文主要观点:单纯把一个RowStore的存储格式换成ColumnStore,而不做其它优化,是达不到一个原生的ColumnStore的性能的。

本文针对的场景:星型的data warehouse,OLAP。

针对RowStore的优化技术

垂直分片

每列对应一个分片,每个分片存储一列数据加主键。重新组合relation的时候要join涉及的列。

全部索引化

每列建一个索引。查询的时候分别在每个索引上应用谓词,再在内存中join结果。对于那些没有谓词的列,可以将其与同query的有谓词的列组成一个索引以避免扫描整个索引。

物化视图

创建好一些物化视图,只包含query需要的列,避免加载不需要的列。

针对ColumnStore的优化技术

压缩

列存的压缩效果往往远好于行存,原因是同一列数据的局部性更好,墒更低。而且列存可以用像RLE这样的不需要解压缩就能扫描的压缩算法。

一列排序好的数据的压缩效果会非常好,列存比较容易实现排序,而行存因为同一列不是连续排列的,这种排序性很难被利用上。

延迟物化

同一个relation的不同列在被加载上来之后,最原始的做法是直接组合成relation,再运算。这样列存与行存就只是存储格式的区别了。我们还可以尽量延迟组合物化的时间,在此之前完成尽量多的操作。一列数据可以表示为数组、位图等数据结构,可以做非常高效的操作。延迟物化的好处:物化前先进行过滤和组合可以避免掉一些不必要的物化;避免不必要的解压缩;cache更友好;更容易应用按块迭代;固定长度的列操作更高效,延迟物化可以避免少量变长列影响其它列的操作。

按块迭代

算子一次处理一个block,函数调用次数更少,不需要额外的提取操作,如果是固定长度的列,可以以数组形式处理。

隐式join

对于星型data warehouse,处理query时比较常见的做法是先join fact table和最具选择性的维表,再在fact table上做aggregation,之后再针对每个谓词join不同的维表。

另一种延迟join的做法是先在维表上应用谓词,再将结果与fact table join,这样能得到两组选中的位置,一组来自维表,一组来自fact table,从维表的位置中我们能得到其它需要的列(但通常是乱序的)。这样依次处理完所有维表,再最终与fact table join得到想要的relation。

本文引入了另一种方法,隐式join。它也是延迟join,但会最小化需要乱序提取的值。它将join改写为应用在fact table上的谓词,这些谓词会通过hash查找(值来自各个维表应用谓词的结果)或区间谓词等方式求值。不同列的隐式join可以同时进行,直到最终选出想要的relation后再与各个维表进行join。

区间谓词

如果应用过谓词后得到了某一列上连续的区间,我们可以将hash查找改写为一个区间上的IN谓词。这种优化没办法应用在非排序列上。对于原本排序好的列,一些重排序操作也会打乱它们的顺序,此时可以应用字典编码来重新给这些列赋值,以保持其有序性,从而可以继续应用这种优化。

性能对比

RowStore的三种模拟ColumnStore的优化都没办法得到特别好的效果。垂直分片只在选择的列少于1/4时有一定优势,超过之后它的空间上的额外开销(重复存储的主键)影响了影响。完全index要求fact table在应用谓词前一定要与维表join。物化视图有效果,但效果仍赶不上ColumnStore。

ColumnStore的优化方法中,按块迭代提升了5%到50%的性能。隐式join提升了50%到75%的性能,主要是区间谓词的贡献。压缩提升了2倍性能,且仍有上升空间(多维度排序维表)。对于一些不需要解压缩就能过滤的场景,压缩的性能提升可以达到10倍。延迟物化提升了3倍性能。

结论

只是将RowStore改造为按列存储是达不到完全针对列存优化的ColumnStore的性能的。这种性能差异的主要因素是压缩和延迟物化,次要因素是按块迭代和隐式join。