Timescale Cloud:性能、扩展、企业级

自托管产品

MST

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

压缩时序数据可将数据块大小减少 90% 以上。这可以节省存储成本,并使查询以极快的速度运行。

启用压缩后,超表中的数据会逐个数据块地进行压缩。当数据块被压缩时,多个记录会组合成一行。该行的列包含一个类似数组的结构,用于存储所有数据。这意味着它将相同的数据存储在单个行中,而不是使用多行来存储数据。因为单行占用的磁盘空间比多行少,所以可以减少所需的磁盘空间量,并且还可以加快查询速度。

例如,如果您的表数据看起来像这样

时间戳设备 ID设备类型CPU磁盘 IO
12:00:01ASSD70.1113.4
12:00:01BHDD69.7020.5
12:00:02ASSD70.1213.2
12:00:02BHDD69.6923.4
12:00:03ASSD70.1413.0
12:00:03BHDD69.7025.2

您可以将其转换为数组形式的单行,如下所示

时间戳设备 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带有时区的时间戳非空
device_id整数非空
device_type整数非空
cpu双精度
disk_io双精度

所有超表都有一个主要维度,用于将表分区成数据块。主要维度在创建超表时给定。在下面的示例中,您可以看到一个经典的以 `time` 列作为主要维度的时序用例。此外,还有 `cpu` 和 `disk_io` 两列包含随时间捕获的值,以及一个 `device_id` 列用于捕获这些值的设备。列可以通过几种不同的方式使用

  • 您可以使用列中的值作为查找键,在上面的示例中,`device_id` 就是此类列的典型示例。
  • 您可以使用列进行表分区。这通常是一个时间列,例如上面示例中的 `time`,但也可以使用其他类型进行表分区。
  • 您可以使用列作为筛选器来缩小选择数据的范围。`device_type` 列就是一个例子,您可以决定只查看固态硬盘 (SSD) 等。其余列通常是您正在收集的值或指标。这些通常以其他方式进行聚合或呈现。`cpu` 和 `disk_io` 列是此类列的典型示例。
SELECT avg(cpu), sum(disk_io)
FROM metrics
WHERE device_type = ‘SSD’
AND time >= now() -1 day’::interval;

当超表中的数据块被压缩时,其中存储的数据会重新组织并以列序而不是行序存储。因此,不可能使用相同未压缩的数据块模式版本,必须创建一个不同的模式。这由 TimescaleDB 自动处理,但这有一些影响:压缩比和查询性能非常依赖于压缩数据的顺序和结构,因此在设置压缩时需要考虑一些因素。超表上的索引不能总是以相同的方式用于压缩数据。

注意

在超表上设置的索引仅用于包含未压缩数据的数据块。Timescale 在压缩期间创建并使用自定义索引来合并 `segmentby` 和 `orderby` 参数,这些参数在读取压缩数据时使用。更多内容将在下一节中介绍。

基于之前的模式,数据筛选应在特定时间段内进行,并且分析以设备粒度完成。这种数据访问模式有利于组织适合压缩的数据布局。

对数据进行排序将极大地影响压缩比和查询性能。在一个维度上变化的行应该彼此靠近。由于我们主要处理时序数据,时间维度是一个很好的选择。大多数情况下,数据以可预测的方式变化,遵循某种趋势。我们可以利用这一事实对数据进行编码,使其存储空间更小。例如,如果您按时间对记录进行排序,它们将按该顺序进行压缩,随后也将按相同顺序访问。

在我们的示例表上使用以下配置设置

ALTER TABLE metrics
SET (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 ms
postgres=# ALTER TABLE metrics
SET (
timescaledb.compress,
timescaledb.compress_segmentby = 'device_id',
timescaledb.compress_orderby='time'
);
ALTER TABLE
Time: 6,607 ms
postgres=# 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_io
FROM metrics
WHERE device_id = 5
GROUP BY device_id;

如您所见,该查询通过将其所有值组合在一起来基于 `device_id` 标识符进行大量工作。我们可以利用这一事实,通过设置压缩来围绕此列中的值对数据进行分段,从而加快这些类型的查询。

在我们的示例表上使用以下配置设置

ALTER TABLE metrics
SET (
timescaledb.compress,
timescaledb.compress_segmentby='device_id',
timescaledb.compress_orderby='time'
);

将生成以下数据布局。

timedevice_iddevice_typecpudisk_ioenergy_consumption
[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=# \timing
Timing is on.
postgres=# SELECT device_id, AVG(cpu) AS avg_cpu, AVG(disk_io) AS avg_disk_io
FROM metrics
WHERE device_id = 5
GROUP BY device_id;
device_id | avg_cpu | avg_disk_io
-----------+--------------------+---------------------
5 | 0.4972598866221261 | 0.49820356730280524
(1 row)
Time: 177,399 ms
postgres=# ALTER TABLE metrics
SET (
timescaledb.compress,
timescaledb.compress_segmentby = 'device_id',
timescaledb.compress_orderby='time'
);
ALTER TABLE
Time: 6,607 ms
postgres=# 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_io
FROM metrics
WHERE device_id = 5
GROUP BY device_id;
device_id | avg_cpu | avg_disk_io
-----------+-------------------+---------------------
5 | 0.497259886622126 | 0.49820356730280535
(1 row)
Time: 42,139 ms
注意

在单个批次中一起压缩的行数(如我们上面看到的)为 1000。如果您的数据块没有足够的数据来创建足够大的批次,您的压缩比将降低。在定义压缩设置时需要考虑到这一点。

关键词