压缩您的时序数据可以让您将数据块大小减少 90% 以上。这节省了存储成本,并保持您的查询以闪电般的速度运行。
当您启用压缩时,超表中的数据会逐块压缩。当数据块被压缩时,多个记录被分组到单行中。此行的列保存类似数组的结构,用于存储所有数据。这意味着它不是使用大量行来存储数据,而是将相同的数据存储在单行中。由于单行占用的磁盘空间小于多行,因此它减少了所需的磁盘空间量,并且还可以加快您的查询速度。
例如,如果您有一个表格,其中的数据看起来有点像这样
时间戳 | 设备 ID | 设备类型 | CPU |
---|---|---|---|
12:00:01 | A | SSD | 70.11 |
12:00:01 | B | HDD | 69.70 |
12:00:02 | A | SSD | 70.12 |
12:00:02 | B | HDD | 69.69 |
12:00:03 | A | SSD | 70.14 |
12:00:03 | B | HDD | 69.70 |
您可以将其转换为数组形式的单行,如下所示
时间戳 | 设备 ID | 设备类型 | CPU | 磁盘 IO |
---|---|---|---|---|
[12:00:01, 12:00:01, 12:00:02, 12:00:02, 12:00:03, 12:00:03] | [A, B, A, B, A, B] | [SSD, HDD, SSD, HDD, SSD, HDD] | [70.11, 69.70, 70.12, 69.69, 70.14, 69.70] | [13.4, 20.5, 13.2, 23.4, 13.0, 25.2] |
本节介绍如何启用原生压缩,然后详细介绍最重要的压缩设置,以帮助您获得最佳的压缩率。
每个表都有不同的模式,但它们确实有一些您需要考虑的共同点。
考虑具有以下属性的表 metrics
列 | 类型 | 排序规则 | 可为空 | 默认值 |
---|---|---|---|---|
time | timestamp with time zone | not null | ||
device_id | integer | not null | ||
device_type | integer | not null | ||
cpu | double precision | |||
disk_io | double precision |
所有超表都有一个主维度,用于将表分区为数据块。主维度在创建超表时给出。在下面的示例中,您可以看到一个经典的时序用例,其中 time
列作为主维度。此外,还有两列 cpu
和 disk_io
包含随时间捕获的值,以及一列 device_id
用于捕获值的设备。列可以用几种不同的方式使用
- 您可以使用列中的值作为查找键,在上面的示例中,
device_id
是此类列的典型示例。 - 您可以使用列来对表进行分区。这通常是时间列,例如上面示例中的
time
,但也可以使用其他类型对表进行分区。 - 您可以使用列作为过滤器来缩小您选择的数据范围。列
device_type
是一个示例,您可以在其中决定仅查看固态硬盘 (SSD)。其余列通常是您正在收集的值或指标。这些通常以其他方式聚合或呈现。列cpu
和disk_io
是此类列的典型示例。
SELECT avg(cpu), sum(disk_io)FROM metricsWHERE device_type = ‘SSD’AND time >= now() - ‘1 day’::interval;
当超表中的数据块被压缩时,存储在其中的数据会重新组织并以列顺序而不是行顺序存储。因此,不可能使用相同未压缩模式版本的数据块,并且必须创建不同的模式。这由 TimescaleDB 自动处理,但它有一些含义:压缩率和查询性能非常依赖于压缩数据的顺序和结构,因此在设置压缩时需要考虑一些因素。超表上的索引并非总是可以以相同的方式用于压缩数据。
注意
在超表上设置的索引仅在包含未压缩数据的数据块上使用。Timescale 创建并使用自定义索引来合并压缩期间的 segmentby
和 orderby
参数,这些参数在读取压缩数据时使用。有关此内容的更多信息,请参见下一节。
根据之前的模式,数据过滤应该发生在某个时间段内,并且分析是在设备粒度上完成的。这种数据访问模式适合组织适合压缩的数据布局。
对数据进行排序将对压缩率和查询性能产生重大影响。在一个维度上变化的行应该彼此靠近。由于我们主要处理时序数据,因此时间维度是一个很好的选择。大多数时候,数据以可预测的方式变化,遵循一定的趋势。我们可以利用这一事实对数据进行编码,使其占用更少的存储空间。例如,如果您按时间顺序对记录进行排序,它们将按该顺序压缩,然后也按相同的顺序访问。
在我们的示例表上使用以下配置设置
ALTER TABLE metricsSET (timescaledb.compress, timescaledb.compress_orderby='time');
将产生以下数据布局。
时间戳 | 设备 ID | 设备类型 | CPU |
---|---|---|---|
[12:00:01, 12:00:01, 12:00:02, 12:00:02, 12:00:03, 12:00:03] | [A, B, A, B, A, B] | [SSD, HDD, SSD, HDD, SSD, HDD] | [70.11, 69.70, 70.12, 69.69, 70.14, 69.70] |
time
列用于对数据进行排序,这使得使用 time
列过滤数据更加高效。
postgres=# select avg(cpu) from metrics where time >= '2024-03-01 00:00:00+01' and time < '2024-03-02 00:00:00+01';avg--------------------0.4996848437842719(1 row)Time: 87,218 mspostgres=# ALTER TABLE metricsSET (timescaledb.compress,timescaledb.compress_segmentby = 'device_id',timescaledb.compress_orderby='time');ALTER TABLETime: 6,607 mspostgres=# SELECT compress_chunk(c) FROM show_chunks('metrics') c;compress_chunk----------------------------------------_timescaledb_internal._hyper_2_4_chunk_timescaledb_internal._hyper_2_5_chunk_timescaledb_internal._hyper_2_6_chunk(3 rows)Time: 3070,626 ms (00:03,071)postgres=# select avg(cpu) from metrics where time >= '2024-03-01 00:00:00+01' and time < '2024-03-02 00:00:00+01';avg------------------0.49968484378427(1 row)Time: 45,384 ms
这使得时间列成为对数据进行排序的完美选择,因为测量值会随着时间的推移而演变。如果您仅将其用作压缩设置,您很可能会获得足够好的压缩率来节省大量存储空间。但是,有效访问数据取决于您的用例和查询。使用此设置,您始终必须通过使用时间维度来访问数据,然后根据任何其他条件过滤所有行。
分段压缩数据应基于您访问数据的方式。基本上,您希望以这样一种方式分段您的数据,以便您可以更轻松地让您的查询在正确的时间获取正确的数据。也就是说,您的查询应该决定您如何分段数据,以便可以优化它们并产生更好的查询性能。
例如,如果您想使用特定的 device_id
值(所有记录或特定时间范围)访问单个设备,您将需要在行访问期间逐个过滤所有这些记录。为了解决这个问题,您可以使用 device_id 列进行分段。如果您正在寻找特定的设备 ID,这将使您能够更快地对压缩数据运行分析查询。
考虑以下查询
SELECT device_id, AVG(cpu) AS avg_cpu, AVG(disk_io) AS avg_disk_ioFROM metricsWHERE device_id = 5GROUP BY device_id;
如您所见,查询根据 device_id
标识符通过将其所有值分组在一起完成大量工作。我们可以利用这一事实通过设置压缩来围绕此列中的值分段数据,从而加快这些类型的查询。
在我们的示例表上使用以下配置设置
ALTER TABLE metricsSET (timescaledb.compress,timescaledb.compress_segmentby='device_id',timescaledb.compress_orderby='time');
将产生以下数据布局。
time | device_id | device_type | cpu | disk_io | 能量消耗 |
---|---|---|---|---|---|
[12:00:02, 12:00:01] | 1 | [SSD,SSD] | [88.2, 88.6] | [20, 25] | [0.8, 0.85] |
[12:00:02, 12:00:01] | 2 | [HDD,HDD] | [300.5, 299.1] | [30, 40] | [0.9, 0.95] |
... | ... | ... | ... | ... | ... |
分段列 device_id
用于根据该列的值将数据点分组在一起。这使得访问特定设备更加高效。
postgres=# \timingTiming is on.postgres=# SELECT device_id, AVG(cpu) AS avg_cpu, AVG(disk_io) AS avg_disk_ioFROM metricsWHERE device_id = 5GROUP BY device_id;device_id | avg_cpu | avg_disk_io-----------+--------------------+---------------------5 | 0.4972598866221261 | 0.49820356730280524(1 row)Time: 177,399 mspostgres=# ALTER TABLE metricsSET (timescaledb.compress,timescaledb.compress_segmentby = 'device_id',timescaledb.compress_orderby='time');ALTER TABLETime: 6,607 mspostgres=# SELECT compress_chunk(c) FROM show_chunks('metrics') c;compress_chunk----------------------------------------_timescaledb_internal._hyper_2_4_chunk_timescaledb_internal._hyper_2_5_chunk_timescaledb_internal._hyper_2_6_chunk(3 rows)Time: 3070,626 ms (00:03,071)postgres=# SELECT device_id, AVG(cpu) AS avg_cpu, AVG(disk_io) AS avg_disk_ioFROM metricsWHERE device_id = 5GROUP BY device_id;device_id | avg_cpu | avg_disk_io-----------+-------------------+---------------------5 | 0.497259886622126 | 0.49820356730280535(1 row)Time: 42,139 ms
注意
在单个批次中一起压缩的行数(如我们上面看到的那些)是 1000。如果您的数据块没有包含足够的数据来创建足够大的批次,您的压缩率将会降低。在定义压缩设置时,需要考虑这一点。
关键词
在此页面上发现问题?报告问题 或 在 GitHub 上编辑此页。