TimescaleDB 使用不同的压缩算法,具体取决于要压缩的数据类型。

对于整数、时间戳和其他类似整数的类型,使用多种压缩方法的组合:delta 编码delta-of-deltasimple-8b游程编码

对于重复值不多的列,使用基于 XOR 的压缩,并辅以一些字典压缩

对于所有其他类型,使用字典压缩

对于整数、时间戳和其他类似整数的类型,TimescaleDB 使用 delta 编码、delta-of-delta、simple 8-b 和游程编码的组合。

simple-8b 压缩方法已扩展,以便可以反向解压缩数据。反向扫描查询在时间序列工作负载中很常见。这意味着这些类型的查询运行速度更快。

Delta 编码通过仅存储数据对象与一个或多个参考对象之间的差异(有时称为 delta)来减少表示数据对象所需的信息量。这些算法在冗余信息较多的情况下效果最佳,并且通常用于版本控制文件系统等工作负载中。例如,这就是 Dropbox 保持文件同步的方式。将 delta 编码应用于时间序列数据意味着您可以使用更少的字节来表示数据点,因为您只需要存储与前一个数据点的 delta。

例如,假设您有一个数据集,该数据集随时间收集 CPU、可用内存、温度和湿度。如果您的时间列存储为整数值,例如自 UNIX 纪元以来的秒数,那么您的原始数据看起来会有点像这样

时间CPU可用内存字节数温度湿度
2023-04-01 10:00:00821,073,741,8248025
2023-04-01 10:00:0598858,993,4598125
2023-04-01 10:00:1098858,904,5838125

使用 delta 编码,您只需要存储每个值相对于前一个数据点的变化量,从而存储更小的值。因此,在第一行之后,您可以使用更少的信息来表示后续行,如下所示

时间CPU可用内存字节数温度湿度
2023-04-01 10:00:00821,073,741,8248025
5 秒16-214,748,36510
5 秒0-88,87600

将 delta 编码应用于时间序列数据利用了大多数时间序列数据集不是随机的,而是表示随时间缓慢变化的事物这一事实。数百万行数据的存储节省可能非常可观,尤其是在值变化很小或根本没有变化的情况下。

Delta-of-delta 编码将 delta 编码更进一步,并将 delta 编码应用于先前已进行 delta 编码的数据。对于数据收集以固定时间间隔发生的时间序列数据集,您可以将 delta-of-delta 编码应用于时间列,这使得只需要存储一系列零。

换句话说,delta 编码存储数据集的一阶导数,而 delta-of-delta 编码存储数据集的二阶导数。

应用于前面示例数据集,delta-of-delta 编码结果如下

时间CPU可用内存字节数温度湿度
2020-04-01 10:00:00821,073,741,8248025
5 秒16-214,748,36510
0 秒0-88,87600

在本例中,delta-of-delta 将时间列中的 5 秒进一步压缩为第二行之后时间列中每个条目的 0,因为五秒的间隔对于每个条目都保持不变。请注意,在 delta-delta 0 值之前,您会在表中看到两个条目,因为您需要两个 delta 进行比较。

这会将完整的 8 字节或 64 位时间戳压缩到仅一位,从而实现 64 倍压缩。

使用 delta 和 delta-of-delta 编码,您可以显着减少需要存储的位数。但是您仍然需要一种有效的方法来存储较小的整数。之前的示例对时间列使用了标准整数数据类型,当进行 delta-delta 编码时,需要 64 位来表示值 0。这意味着即使您只存储整数 0,您仍然消耗 64 位来存储它,因此您实际上没有节省任何空间。

Simple-8b 是存储可变长度整数的最简单和最小的方法之一。在这种方法中,整数存储为一系列固定大小的块。对于每个块,块内的每个整数都由表示该块中最大整数所需的最小位长度表示。每个块的第一个位表示该块的最小位长度。

这种技术的优点是只需要为给定块存储一次长度,而不是为每个整数存储一次。由于块的大小是固定的,因此您可以从存储的整数的大小推断出每个块中的整数数量。

例如,如果您想要存储随时间变化的温度,并且您应用了 delta 编码,您可能最终需要存储这组整数

温度 (deltas)
1
10
11
13
9
100
22
11

对于 10 位数字的块大小,您可以将这组整数存储为两个块:一个块存储 5 个 2 位数字,第二个块存储 3 个 3 位数字,如下所示

{2: [01, 10, 11, 13, 09]} {3: [100, 022, 011]}

在本例中,即使某些数字必须用前导 0 填充,两个块都存储了大约 10 位数字的数据。您可能还会注意到,第二个块仅存储 9 位数字,因为 10 不能被 3 整除。

Simple-8b 以这种方式工作,只是它使用二进制数而不是十进制数,并且它通常使用 64 位块。一般来说,整数越长,每个块中可以存储的整数数量就越少。

Simple-8b 对整数的压缩效果非常好,但是,如果您有大量相同值的重复项,则可以使用游程编码获得更好的压缩效果。此方法适用于不经常更改的值,或者如果较早的转换删除了更改。

游程编码是经典的压缩算法之一。对于具有数十亿个连续零的时间序列数据,或者甚至是具有数百万个相同重复字符串的文档,游程编码效果非常好。

例如,如果您想要存储随时间变化很小的温度,并且您应用了 delta 编码,您可能最终需要存储这组整数

温度 (deltas)
11
12
12
12
12
12
12
1
12
12
12
12

对于这样的值,您不需要存储值的每个实例,而是存储游程的长度或重复次数。您可以将这组数字存储为 {run; value} 对,如下所示

{1; 11}, {6; 12}, {1; 1}, {4; 12}

此技术使用 11 位数字的存储空间 (1, 1, 1, 6, 1, 2, 1, 1, 4, 1, 2),而不是最佳可变长度整数系列所需的 23 位数字 (11, 12, 12, 12, 12, 12, 12, 1, 12, 12, 12, 12)。

游程编码也用作许多更高级算法的构建块,例如 Simple-8b RLE,这是一种结合了游程和 Simple-8b 技术的算法。TimescaleDB 实现了 Simple-8b RLE 的变体。此变体使用与标准 Simple-8b 不同的尺寸,以便处理 64 位值和 RLE。

对于重复值不多的列,TimescaleDB 使用基于 XOR 的压缩。

标准的基于 XOR 的压缩方法已扩展,以便可以反向解压缩数据。反向扫描查询在时间序列工作负载中很常见。这意味着使用反向扫描的查询运行速度更快。

浮点数通常比整数更难压缩。定长整数通常具有前导零,但浮点数通常使用其所有可用位,特别是当它们从十进制数转换而来时,十进制数无法在二进制中精确表示。

像 delta 编码这样的技术不适用于浮点数,因为它们不能充分减少位数。这意味着大多数浮点压缩算法要么复杂且缓慢,要么会截断有效数字。少数简单且快速的无损浮点压缩算法之一是基于 XOR 的压缩,它建立在 Facebook 的 Gorilla 压缩之上。

XOR 是二进制函数 exclusive or。在这种算法中,将连续的浮点数与 XOR 进行比较,差异会导致存储一位。第一个数据点在不压缩的情况下存储,后续数据点使用其 XOR 值表示。

对于不是整数或浮点数的值,TimescaleDB 使用字典压缩。

字典压缩是最早的无损压缩算法之一,是许多流行压缩方法的基础。字典压缩也可以在计算机科学以外的领域找到,例如医学编码。

字典压缩不是直接存储值,而是通过创建一个可能出现的值列表,然后存储一个指向包含唯一值的字典的索引来工作。这种技术非常通用,可以用于任何数据类型,并且在您有一组有限的频繁重复的值时效果特别好。

例如,如果您有前面显示的温度列表,但您想要一个额外的列来存储每次测量的城市位置,您可能会有一组这样的值

城市
纽约
旧金山
旧金山
洛杉矶

您可以存储一个字典,而不是直接存储所有城市名称,如下所示

{0: "New York", 1: "San Francisco", 2: "Los Angeles",}

然后,您可以在列中仅存储索引,如下所示

城市
0
1
1
2

对于具有大量重复的数据集,这可以提供显着的压缩。在本例中,每个城市名称的平均长度为 11 字节,而索引永远不会超过 4 字节长,从而将空间使用量减少近 3 倍。在 TimescaleDB 中,索引列表使用 Simple-8b+RLE 方法进一步压缩,从而使存储成本更低。

字典压缩并不总是能带来节省。如果您的数据集没有大量重复的值,那么字典的大小与原始数据的大小相同。TimescaleDB 会自动检测到这种情况,并在这种情况下回退到不使用字典。

关键词

在此页面上发现问题?报告问题 或 在 GitHub 上编辑此页面