为什么时序数据库的压缩率能到20:1

尔悦

2026-10-23 /

时序数据压缩:增量编码与列存的协同原理

时序数据的可压缩本质

时序数据之所以能实现远超通用数据库的压缩比,根源在于它有三个极其显著的统计特征:时间戳单调递增、相邻数值高度相关、重复值密集。

数据类型

时间戳单调性

数值相关性

重复率

典型原始大小(64点)

典型压缩后大小

温度传感器

严格单调

相邻点相关系数 > 0.95

低

1280B

150~250B

压力传感器

严格单调

相关系数 > 0.90

中

1280B

200~350B

开关状态

严格单调

低(跳变时)

极高(99.9%是0)

1280B

20~50B

振动波形

严格单调

中等(周期性)

低

1280B

400~600B

GPS坐标

严格单调

中

低

1920B

300~500B

同样是64个原始数据点,温度数据的压缩比可以到8:1甚至15:1,而通用数据库对随机数据的压缩率通常只有2:1~3:1。差异就在于时序数据库识别并利用了数据的时序特征。

增量编码系列算法

针对时间戳的单调性,最直接的方案是增量编码(Delta Encoding)——不存储每个点的绝对时间戳,只存它与前一个点的差值。对于固定采样率的传感器(1秒/次),这个差值几乎恒为1,000,000微秒(毫秒精度下),只需存储一次基准值+N-1个小整数。

进一步的优化是DoubleDelta编码:不仅对时间戳做一次差分,还对差值序列再做一次差分。对于时间戳存在微小抖动的场景(网络传输延迟导致±5ms波动),差值序列仍然有强相关性。

时间戳序列(ms)

Delta

DoubleDelta

存储位数(假设10位/值)

1000, 2000, 3001, 4000, 5002, 6000

1000, 1001, 999, 1002, 998

1, -2, 3, -4

6个差值×可变长度(实际 < 平均10bit)

对于数值列,类似的思想发展出了For(Frame of Reference)编码:存储该列的最小值作为基准值,每个点只存储它与基准值的差值。如果一个温度序列在25.3-25.8之间波动,基准值取25.3后,差值全部在0-0.5之间,可以用更小的整数表示。

DFCM(Differential Frame of Reference with Compression Mask)是For的进阶方案,额外引入一个Compression Mask位平面标记哪些点可以被高压缩。

浮点数的特殊挑战

时序数据中大量是浮点数(IEEE 754格式),传统增量编码直接应用在浮点表示上效果很差,因为浮点的内部表示不是连续的——25.0和25.1在浮点表示上的位差异可能比25.0和26.0还大。

XOR Encoding巧妙地解决了这个问题。核心观察是:两个数值如果很接近,它们的IEEE 754表示做XOR操作后,前导零(Leading Zeros)和尾随零(Trailing Zeros)都很多,有效位(Significant Bits)长度很短。编码时存储前导零长度+有效位长度+有效位内容,相邻值的前导零长度相同还可以进一步压缩。

25.0 的 IEEE 754 表示:
010000011 10010000000000000000000   (32位,单精度)

25.1 的 IEEE 754 表示:
010000011 10010001100110011001100

XOR 结果:
000000000 00000001100110011001100
前导零 = 9位,有效位长度 = 23位 → 存储只需要记录这两个数字和有效位内容

XOR编码与DoubleDelta结合(先对数值做一次Delta,再对Delta序列做XOR),就是业界著名的Gorilla Compression算法——Facebook在其Gorilla时序数据库中首创,TDengine和Prometheus TSDB都采用了类似思路。

熵编码:最后一公里

增量编码、XOR编码等步骤完成后,输出的是一串变长整数。这些整数的分布并不均匀——小数值出现概率远高于大数值,因为相邻时间戳的抖动通常在几个毫秒以内。熵编码(Entropy Coding)就是利用这种概率分布的不均匀性做最后压缩。

熵编码算法

特点

适用场景

Huffman Coding

统计每个值的出现频率,频率高的用短码

值的概率分布极不均匀时最优

Arithmetic Coding

编码效率略高于Huffman,但实现复杂

需要最优压缩率时

LZ4

快速压缩/解压,无字典开销

热数据、需要高速解压的场景

ZSTD

LZ4的下一代,压缩率更高,速度相当

平衡压缩率和速度的通用选择

GZIP/DEFLATE

经典算法,兼容性最好

需要跨平台兼容时

TDengine在增量编码和XOR编码完成后,用LZ4做熵编码层。原因很直接:时序数据的查询端需要频繁解压,LZ4的解压速度(单核>500MB/s)远优于ZSTD/GZIP,实际工程中压缩率差异(约10%)远不如解压速度重要。

列式存储的协同效应

如果把压缩算法比作”手术刀”,列式存储就是”手术台”——它让手术刀能精准命中病灶。时序数据按时间窗切块存储,每个块内同一列的数值在时间上是连续的,这种物理存储布局让增量编码和XOR编码的效果最大化。

行式存储的问题在于,一条记录包含多个列的数值,每个数值的分布特征混合在一起,无法针对性优化。列式存储把温度列、压力列、时间戳列分开存,每一列内部的数值特征极其单一:温度列都是单调或缓变的浮点数,时间戳列都是严格单调的整数序列。

存储方式

同一列数据的聚集度

压缩算法适配度

典型压缩率

行式存储

低(多列混合)

通用压缩器

2:1 ~ 4:1

列式存储

高(单类型聚集)

增量+XOR+熵编码

10:1 ~ 20:1

TDengine的存储引擎按超级表维度切块,每个数据块包含多个子表在同一时间窗口内的记录,但同一列的数据在物理上是连续存放的。这就是为什么TDengine在压缩率上能显著优于行式存储的数据库。

压缩率 vs CPU开销

任何压缩都是空间换时间的Trade-off。压缩率越高,编码和解码的CPU开销越大。

压缩层级

额外CPU开销(编码+解码)

压缩率增益

推荐配置

仅增量编码

< 5%

3:1 ~ 5:1

低延迟场景

增量 + XOR

10% ~ 20%

8:1 ~ 12:1

通用场景(TDengine默认)

增量 + XOR + ZSTD

30% ~ 50%

12:1 ~ 18:1

冷数据归档

增量 + XOR + GZIP

> 60%

15:1 ~ 20:1

长期冷存储

TDengine允许按数据块配置不同的压缩策略——热数据用轻量压缩保证查询速度,冷数据用高强度压缩最大化存储效率。

结语

时序数据的高压缩率不是一个黑魔法,而是对数据本质特征逐层挖掘的结果。DoubleDelta抓住了时间戳的单调性,XOR编码抓住了浮点数的接近性,列式存储让同类型数据聚集,熵编码吃掉最后一层统计冗余。TDengine的压缩实现把这些原理组合在一起,在CPU开销和压缩率之间取了一个工程化的平衡。对于任何构建或评估时序数据库的团队,理解压缩原理不是优化手段的选择,而是从根本上判断架构设计方向是否正确的标尺。