分层存储架构以低成本的对象存储层补充了 Timescale 的标准高性能存储层;一个基于 Amazon S3 的对象存储。特别是,用户能够将超表块透明地分层到 Timescale 平台上的对象存储层中,以实现高度可扩展的长期存储。
但这不仅仅是一个存档!分层后,这些块仍然可以在数据库中使用标准 SQL 进行完全直接查询。给定超表的块现在可以跨越标准存储(以块形式)和对象存储层(以对象形式),但单个 SQL 查询使用 TimescaleDB 的块排除算法透明地从适当的块中提取数据。
事实上,对象存储层中的块以压缩的列式格式存储(与数据库内部的格式不同,以实现跨各种平台的更好互操作性)。这种格式允许在更长的时间段内进行更有效的列式扫描,而 Timescale 使用其他元数据和查询优化来减少需要从对象存储层中提取的数据量以满足查询。
让我们开始吧!
首先,从 Timescale 云控制台的 UI 中启用分层存储。
在具有超表的现有数据库服务中,您可以通过超表的自动策略或通过特定块的手动命令将块分层到对象存储层。虽然用户可能在生产环境中采用自动策略,但手动命令是开始试验分层存储的好方法。
用户可以通过显式指定块的名称将单个块移动到对象存储层。
SELECT tier_chunk('_timescaledb_internal._hyper_2_3_chunk');
要获取要分层的块的名称,您可以使用块信息视图。例如
SELECT chunk_schema, chunk_name, range_start, range_end FROM timescaledb_information.chunks WHERE hypertable_name = 'metrics_table';-[ RECORD 1 ]+-----------------------chunk_schema | _timescaledb_internalchunk_name | _hyper_2_3_chunkrange_start | 2017-08-02 20:00:00-04range_end | 2017-08-09 20:00:00-04
在特定块上执行 tier_chunk 命令不会立即同步地将块移动到对象存储层,而是安排块进行迁移。在后台,云服务将异步地将块迁移到对象存储层,并且只有在块已持久存储在对象存储层中后,才将其标记为已迁移(并从数据库的主存储中删除)。
您可以使用此查询查看分层队列中的块,即计划分层的块。
SELECT * FROM timescaledb_osm.chunks_queued_for_tiering ;-[ RECORD 1 ]-----+-----------------hypertable_schema | publichypertable_name | metrics_tablechunk_name | _hyper_2_3_chunk
对于较小的块,这种异步迁移应该在几秒钟或几分钟内完成,尽管块在迁移过程中仍然可以完全查询:数据库引擎会继续访问主存储中的块,直到它完全切换到使用对象存储层中的块。是的,您可以无缝地分层压缩块,尽管它在分层到对象存储层后使用不同的存储表示形式。
用户可以创建分层策略来自动将数据移动到对象存储,以便任何时间范围在 move_after 阈值之前的时间范围的块都将被移动到对象存储层。这种基于间隔阈值的策略类似于压缩和数据保留策略中的年龄阈值。
分层策略在块级别运行,因此策略定期启动一项作业,该作业会异步地将选定的块移动到对象存储层。默认情况下,分层策略每小时在您的数据库上运行;这可以通过 alter_job 修改。
示例
SELECT add_tiering_policy('metrics', INTERVAL '4 weeks');
我们还提供了一个删除分层策略接口,如果您想停止分层数据。
此函数删除自动分层的后台作业。但是,任何已经移动到对象存储层的块都将保留在该位置。任何计划分层的块也不会受到此命令的影响。
您可以通过数据库中的标准信息视图查看分层到对象存储层的一组块
SELECT * FROM timescaledb_osm.tiered_chunks;-[ RECORD 1 ]-----+-----------------------hypertable_schema | publichypertable_name | metricschunk_name | _hyper_1_4_chunkrange_start | 2022-04-28 00:00:00+00range_end | 2022-05-05 00:00:00+00-[ RECORD 2 ]-----+-----------------------hypertable_schema | publichypertable_name | metricschunk_name | _hyper_1_1_chunkrange_start | 2022-05-26 00:00:00+00range_end | 2022-06-02 00:00:00+00
一旦超表跨存储层进行分层,您就可以像往常一样继续查询它,包括将其与其他关系表连接,以及所有这些 SQL 功能。
考虑一个具有标准设备表和度量超表的简单数据库。
CREATE TABLE devices ( id integer, description text);CREATE TABLE metrics ( ts timestamp with time zone, device_id integer, val float);SELECT create_hypertable('metrics', 'ts');
将数据插入表后,您可以将超表中的一些数据分层到对象存储层。对信息视图的简单查询说明了哪些块分层到对象存储层。
SELECT chunk_name, range_start, range_end FROM timescaledb_osm.tiered_chunks where hypertable_name = 'metrics';chunk_name | range_start | range_end------------------+------------------------+------------------------_hyper_2_4_chunk | 2015-12-31 00:00:00+00 | 2016-01-07 00:00:00+00_hyper_2_3_chunk | 2017-08-17 00:00:00+00 | 2017-08-24 00:00:00+00(2 rows)
默认情况下,查询对象存储层是被禁用的。让我们先启用它,然后运行查询。有关从对象存储层启用读取的详细步骤,请参见查询分层数据。
set timescaledb.enable_tiered_reads = true;
此查询仅从对象存储层中获取数据。这根据查询指定的 WHERE 子句和上面列出的此超表的块范围是有意义的。
EXPLAIN SELECT * FROM metrics where ts < '2017-01-01 00:00+00';QUERY PLAN---------------------------------------------------------------------Foreign Scan on osm_chunk_2 (cost=0.00..0.00 rows=2 width=20)Filter: (ts < '2017-01-01 00:00:00'::timestamp without time zone)Match tiered objects: 1Row Groups:_timescaledb_internal._hyper_2_4_chunk: 0(5 rows)
如果您的查询谓词永远不需要访问对象存储层,它将只处理存储在常规存储中的那些块;在这种情况下,时间谓词引用尚未分层到对象存储层的较新数据。此查询根本不访问对象存储层。我们知道,因为计划中的Match tiered objects :0
表明没有分层数据与查询约束匹配。
EXPLAIN SELECT * FROM metrics where ts > '2022-01-01 00:00+00';QUERY PLAN------------------------------------------------------------------------------------------------------------------Append (cost=0.15..25.02 rows=568 width=20)-> Index Scan using _hyper_2_5_chunk_metrics_ts_idx on _hyper_2_5_chunk (cost=0.15..22.18 rows=567 width=20)Index Cond: (ts > '2022-01-01 00:00:00'::timestamp without time zone)-> Foreign Scan on osm_chunk_2 (cost=0.00..0.00 rows=1 width=20)Filter: (ts > '2022-01-01 00:00:00'::timestamp without time zone)Match tiered objects: 0Row Groups:(7 rows)
以下是另一个与 JOIN 相关的示例,它不访问分层数据。
EXPLAIN SELECT ts, device_id, description FROM metricsJOIN devices ON metrics.device_id = devices.idWHERE metrics.ts > '2023-08-01';QUERY PLAN--------------------------------------------------------------------------------Hash Join (cost=32.12..184.55 rows=3607 width=44)Hash Cond: (devices.id = _hyper_4_9_chunk.device_id)-> Seq Scan on devices (cost=0.00..22.70 rows=1270 width=36)-> Hash (cost=25.02..25.02 rows=568 width=12)-> Append (cost=0.15..25.02 rows=568 width=12)-> Index Scan using _hyper_4_9_chunk_metrics_ts_idx on _hyper_4_9_chunk (cost=0.15..22.18 rows=567 width=12)Index Cond: (ts > '2023-08-01 00:00:00+00'::timestamp withtime zone)-> Foreign Scan on osm_chunk_3 (cost=0.00..0.00 rows=1 width=12)Filter: (ts > '2023-08-01 00:00:00+00'::timestamp with timezone)Match tiered objects: 0Row Groups:(11 rows)
让我们更深入地了解数据如何在 S3 上组织。当块被分层时,它们被写出为 Parquet 对象。Parquet 是一种列式存储格式。在一个 Parquet 文件中,我们将一组行组合在一起形成一个行组。在行组中,单个列的值(跨多个行)一起存储。查询计划器在多个阶段优化对对象存储层的访问
- 块修剪 - 仅匹配满足查询约束的块。这是通过查看超表的维列元数据(通常是时间戳)来完成的。
- 行组修剪 - 识别 Parquet 对象中满足查询的行组。
- 列修剪 - 仅获取查询请求的列。
以下查询针对的是分层在 S3 上的更大数据集,您可以在此处看到查询优化在起作用。EXPLAIN 将说明哪些块正在从对象存储层中提取。首先,我们只从对象存储层中的块 42、43 和 44 中获取数据。然后我们修剪行组并限制获取到 Parquet 对象中可能与查询过滤器匹配的偏移量的子集。我们只获取设备_uuid、传感器_id 和观测_at 列的数据,因为查询只需要这 3 列。
EXPLAIN ANALYZESELECT count(*) FROM( SELECT device_uuid, sensor_id FROM public.device_readingsWHERE observed_at > '2023-08-28 00:00+00' and observed_at < '2023-08-29 00:00+00'GROUP BY device_uuid, sensor_id ) q;QUERY PLAN-------------------------------------------------------------------------------------------------Aggregate (cost=7277226.78..7277226.79 rows=1 width=8) (actual time=234993.749..234993.750 rows=1 loops=1)-> HashAggregate (cost=4929031.23..7177226.78 rows=8000000 width=68) (actual time=184256.546..234913.067 rows=1651523 loops=1)Group Key: osm_chunk_1.device_uuid, osm_chunk_1.sensor_idPlanned Partitions: 128 Batches: 129 Memory Usage: 20497kB Disk Usage: 4429832kB-> Foreign Scan on osm_chunk_1 (cost=0.00..0.00 rows=92509677 width=68) (actual time=345.890..128688.459 rows=92505457 loops=1)Filter: ((observed_at > '2023-08-28 00:00:00+00'::timestamp with time zone) AND (observed_at < '2023-08-29 00:00:00+00'::timestamp with time zone))Rows Removed by Filter: 4220Match tiered objects: 3Row Groups:_timescaledb_internal._hyper_1_42_chunk: 0-74_timescaledb_internal._hyper_1_43_chunk: 0-29_timescaledb_internal._hyper_1_44_chunk: 0-71S3 requests: 177S3 data: 224423195 bytesPlanning Time: 6.216 msExecution Time: 235372.223 ms(16 rows)
您可以使用 Timescale 的数据保留策略和 API来删除分层数据。
关键字
在本页上发现问题?报告问题 或 在 GitHub 上编辑此页面。