Timescale 云:性能、规模、企业级

自托管产品

MST

TimescaleDB v2.18.0 起为旧 API,已被 hypercore 取代。

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

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

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

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

对于整数、时间戳和其他类似整数的类型,TimescaleDB 结合使用增量编码、增量-增量编码、Simple-8b 和游程编码。

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

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

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

时间CPUmem_free_bytes温度湿度
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

通过增量编码,您只需存储每个值相对于前一个数据点的变化量,从而存储更小的值。因此,在第一行之后,您可以用更少的信息表示后续行,例如这样

时间CPUmem_free_bytes温度湿度
2023-04-01 10:00:00821,073,741,8248025
5 秒16-214,748,36510
5 秒0-88,87600

将增量编码应用于时序数据利用了这样一个事实:大多数时序数据集并非随机的,而是代表随时间缓慢变化的事物。数百万行的数据存储节省量可能相当可观,特别是当值变化很小或根本没有变化时。

增量-增量编码将增量编码更进一步,对已经过增量编码的数据应用增量编码。对于以规则间隔进行数据收集的时序数据集,您可以对时间列应用增量-增量编码,这最终只需存储一系列零。

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

应用于前面的示例数据集,增量-增量编码的结果是这样

时间CPUmem_free_bytes温度湿度
2020-04-01 10:00:00821,073,741,8248025
5 秒16-214,748,36510
0 秒0-88,87600

在这个例子中,增量-增量编码将时间列中的 5 秒进一步压缩到第二行之后每个条目的 0,因为 5 秒的间隔对每个条目保持不变。请注意,在增量-增量 0 值之前您会看到两个条目,因为您需要比较两个增量。

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

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

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

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

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

温度(增量)
1
10
11
13
9
100
22
11

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

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

在此示例中,两个块都存储了大约 10 位数据,尽管有些数字必须用前导 0 填充。您可能还会注意到第二个块只存储了 9 位,因为 10 不能被 3 整除。

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

Simple-8b 对整数的压缩效果非常好,但是,如果出现大量重复的相同值,使用游程编码可以获得更好的压缩效果。此方法适用于值不常变化,或者如果先前的转换移除了变化的情况。

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

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

温度(增量)
11
12
12
12
12
12
12
1
12
12
12
12

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

{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 的压缩方法已扩展,以便数据可以反向解压。反向扫描查询在时序工作负载中很常见。这意味着使用反向扫描的查询运行速度更快。

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

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

XOR 是二进制函数 `异或`。在此算法中,连续的浮点数通过 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 上。