3. 列式存储的编码技术:字典编码、RLE、Delta Encoding 原理与对比

聊到列式存储,绕不开的一个话题就是编码。说白了,列存之所以能比行存省那么多空间、查那么快,编码技术功不可没。我刚开始接触列存时,觉得不就是压缩嘛,后来踩了几个坑才明白——编码不只是压缩,它直接决定了查询引擎能不能跳过大量数据。

这一节,我们重点拆解三种最基础的编码:字典编码、RLE(游程编码)、Delta Encoding(差值编码)。我会结合项目里的实际案例,把它们的原理、适用场景、优缺点讲透。

3.1 字典编码:用ID换空间

字典编码的思路很朴素——把重复出现的值映射成更短的整数ID。你想想看,如果一列里只有几十种不同的值,却有几百万行,那存原始字符串多浪费?

具体做法分两步:

  1. 构建字典:扫描整列,为每个唯一值分配一个递增ID。
  2. 替换数据:把原始值替换成对应的ID。

举个例子,假设有一列“城市”:

原始数据:北京, 上海, 北京, 广州, 上海, 北京
字典映射:北京→0, 上海→1, 广州→2
编码后数据:0, 1, 0, 2, 1, 0

我在一个用户画像系统里用过字典编码。当时有个“性别”列,只有“男”“女”“未知”三种值,原始字符串占3个字节,编码后每个值只用1个bit(3种值需要2bit)。整列压缩比直接干到1:12。不过要注意,字典本身也要存,如果唯一值太多,字典可能比数据还大——那就得不偿失了。

适用场景:基数低(唯一值数量少)的列,比如枚举类型、状态码、分类标签。

不适用场景:唯一值接近行数的列,比如UUID、时间戳(精度到毫秒)。

3.2 RLE:把重复变成计数

RLE(Run-Length Encoding)的原理更直接——遇到连续重复的值,只存一次值和重复次数。我习惯叫它“数数编码”。

还是用数据说话:

原始数据:A, A, A, B, B, C, C, C, C
RLE编码:(A,3), (B,2), (C,4)

你看,9个值变成了3个二元组。如果列里连续重复很多,压缩效果非常恐怖。我曾经处理过一个“订单状态”列,数据按时间排序后,大量连续行都是“已完成”,RLE跑完,压缩比超过1:20。

一个小技巧:RLE对数据顺序敏感。如果数据是随机分布的,连续重复很少,RLE反而可能膨胀(因为要额外存计数)。所以很多列存系统会在写入前先做排序,让相同值尽量聚在一起。

但RLE有个坑——更新很麻烦。你想,如果中间某行状态变了,后面的RLE段都得重算。所以RLE更适合写少读多的场景,比如日志归档、历史快照。

3.3 Delta Encoding:存差值,不存原值

Delta Encoding的思路是:不存原始值,只存相邻值之间的差值。特别适合有序的数值列,比如时间戳、自增ID、传感器读数。

看个例子:

原始时间戳(秒):100, 102, 105, 110, 120
Delta编码:100(基准值), +2, +3, +5, +10

如果差值很小,可以用更少的bit来存。比如原始时间戳是64位整数,差值可能只需要8位甚至更少。我在物联网平台里用过这个,设备上报的时间戳间隔很规律,差值基本都在0~255之间,压缩比轻松到1:6以上。

注意:Delta Encoding要求数据有序。如果乱序,差值可能很大甚至负数,压缩效果会打折扣。另外,如果某一行数据损坏,后面的所有行都会解码错误——所以一般会配合校验和或周期性地存一个完整基准值。

3.4 三种编码的对比

下面这张表是我自己总结的,平时选编码时直接对照着看:

特性 字典编码 RLE Delta Encoding
核心思想 值→ID映射 连续重复→计数 存差值代替原值
最佳场景 低基数列 高重复率列 有序数值列
压缩比 取决于基数/行数比 取决于连续重复长度 取决于差值大小
解码速度 快(查表) 极快(展开即可) 中等(需累加)
支持随机访问 是 否(需从头解码) 否(需从基准值开始)
更新代价 低(字典可追加) 高(需重算段) 高(后续值需重算)
典型应用 Parquet字典页 ORC的RLEv2 时序数据库列存

嗯,这里要注意,实际生产环境中很少只用一种编码。比如Parquet里,同一列的不同数据页可能用不同编码——基数高的页用Delta,基数低的页用字典。这叫“自适应编码”,后面章节会细讲。

3.5 编码选择的核心逻辑

我个人的经验是,选编码前先问自己三个问题:

  1. 这列的唯一值有多少?——少就用字典,多就考虑其他。
  2. 数据是否有序?——有序优先考虑Delta,无序看重复率。
  3. 查询模式是什么?——频繁点查要支持随机访问,字典更合适;全列扫描RLE和Delta都行。

下面这张图是我画的一个决策流程,帮你快速定位:

编码选择决策流程 原始数据列 唯一值数量 < 行数 1%? 是 字典编码 否 数据是否有序? 是 Delta Encoding 否 连续重复比例高? 是 RLE 否 混合/自适应编码 提示:实际系统中常组合使用,例如字典+RLE对低基数高重复列效果极佳

说实话,没有银弹。我在一个项目里试过对“用户ID”列用字典编码,结果唯一值接近行数,字典比数据还大,最后换成了Delta Encoding,配合排序,效果好了很多。所以,一定要先分析数据分布,再选编码。

3.6 避坑指南

最后分享几个我踩过的坑:

  • 别迷信压缩比:压缩比高不代表查询快。RLE解压极快,但字典需要查表,Delta需要累加,解码开销不一样。
  • 注意编码的“副作用”:比如字典编码后,数据页里存的是ID,但查询时如果过滤条件是原始值,需要先查字典转成ID——这个转换本身有成本。
  • 小心编码嵌套:有些系统允许对编码后的数据再编码(比如对RLE的计数再做Delta),理论上能进一步压缩,但解码链路变长,容易出bug。我见过一个线上事故,就是因为嵌套编码的解码逻辑有边界条件没处理好,导致查询结果错乱。