列式压缩:轻量编码怎么选、Parquet 与 ORC 怎么写、编码数据上怎么算

💡 原文中文,约28800字,阅读约需69分钟。
📝

内容提要

本文比较列存轻量编码与通用压缩。在相同TPC-H数据上,Parquet默认字典对有序整数压缩效果反而更差,DELTA_BINARY_PACKED更小;ORC对整数不建字典,字符串仅判断一次,容易选错编码。排序可大幅改变压缩效果。zstd的收益多来自文本列。DuckDB采用全量分析,BtrBlocks通过采样选择编码更优,但采样存在固定开销与偏差。

🔎

延伸解读

编码选择比压缩算法更重要

文章通过TPC-H数据表明,同一列使用不同轻量编码,字节数可能相差数倍。例如有序整数列l_orderkey,Parquet默认字典编码为1,894,373字节,而DELTA_BINARY_PACKED仅377,246字节。这提醒我们,在考虑通用压缩算法(如zstd)之前,应先关注列存格式的轻量编码选择,因为它直接利用数据类型和分布规律,往往能带来更大的压缩收益。

排序对压缩效果影响显著

将表按低基数列排序后,Parquet的l_returnflag列块从146,567字节降至71字节,ORC的同一列也从311,391字节降至7,097字节。排序能增加相邻值的重复性,使游程编码和差分编码更有效。但不同编码受益程度不同:字典编码按出现顺序编号,无法利用排序后的顺序关系;而差分编码则能大幅受益。因此,合理设计排序键可以显著提升压缩率。

通用压缩的收益集中在文本列

在TPC-H lineitem上,zstd将Parquet文件从29,204,234字节压到15,208,332字节,其中12,674,942字节的节省来自自由文本列l_comment。其余15列仅减少约12%。这说明通用压缩主要对高基数、无结构的文本有效,而对已经过轻量编码的数值列收益有限。因此,按列决定是否使用通用压缩比按文件统一压缩更合理。

采样选择编码存在偏差风险

BtrBlocks式采样选择在1%样本上,160个块中有140个与穷举最优一致,总字节仅多0.29%。但选错的20个块全部来自l_discount和l_tax,原因是字典等方案有固定开销,在小样本上比例被高估,导致估算反转。这表明采样选择对包含固定开销的编码可能产生系统性偏差,尤其在低基数数据上,需要更精细的样本设计或修正方法。

❓

Q&A

Parquet 和 ORC 在整数列编码上有什么主要区别?

Parquet 默认对整数列先尝试字典编码,字典页达到 1 MiB 后回退为 PLAIN;ORC 的整数列只有 RLEv1 和 RLEv2 两种编码,不建字典。因此对于有序整数,Parquet 的字典可能比 DELTA_BINARY_PACKED 大很多,而 ORC 的 RLEv2 在低基数整数上可能不如 Parquet 字典。

为什么 Parquet 对有序整数使用字典编码反而效果更差?

因为字典编码按值出现顺序分配编号,不保留值的大小关系,无法利用有序性。对于有序整数,相邻差分很小,DELTA_BINARY_PACKED 能大幅压缩,而字典需要存储所有不同值及其索引,导致体积更大。例如 l_orderkey 列,字典版比 DELTA_BINARY_PACKED 大 5 倍。

ORC 如何决定是否为字符串列使用字典编码?

ORC 在第一个行组(默认 10,000 行)结束时检查不同值比例,如果比例小于等于阈值(默认 0.8)则使用字典编码,否则使用直接编码。这个判断只做一次,之后不再更改。

排序如何影响列式存储的压缩效果?

排序可以显著改变压缩效果。将表按低基数列排序后,这些列会产生长游程,适合 RLE 编码,压缩率大幅提升。例如 l_returnflag 列在排序后,Parquet 列块从 146,567 字节降到 71 字节。但排序也可能打乱原本有序的列,导致其压缩效果变差。

BtrBlocks 的采样选择编码与 Parquet 的固定规则相比有什么优势?

BtrBlocks 通过采样(1% 数据)为每个块选择最优编码,在 TPC-H 数据上,140/160 个块与穷举最优一致,总字节仅比最优多 0.29%。而 Parquet 使用固定规则(先字典后回退),可能选择次优编码。但采样存在固定开销偏差,对于有固定开销的编码(如字典)在小样本上可能被高估。

在编码数据上直接执行计算能节省多少工作?

在字典编码和 RLE 上直接执行可以避免解码。例如,对于等值查询,只需在字典中查找一次,然后比较整数编码,节省字符串比较;对于聚合查询,RLE 允许一次处理整个游程。在排序后的数据上,GROUP BY 操作从 600,572 次哈希更新减少到 3 次。

🏷️

标签

➡️

继续阅读