简介
如果您在 ClickHouse 中分析的数据随着时间的推移而增长,您可能需要计划定期移动、删除或汇总较旧的数据。通常,您在这里的选择将取决于您的数据保留要求以及查询 SLA 是否根据数据年龄而有所不同。例如,虽然通常需要以最高可用粒度存储最新数据的全部维度,但以较低级别细节存储历史数据可能可选。
管理数据生命周期可以帮助优化存储并提高查询性能。ClickHouse 具有一个简单但功能强大的数据生命周期管理工具,使用 DDL 语句的 TTL
子句配置。
在本博文中,我们将探讨 TTL
子句以及如何使用它来解决一些数据管理任务。
自动删除过期数据
有时,存储较旧的数据不再有意义,可以将其从 ClickHouse 中删除。这通常称为保留策略。数据删除是在后台自动完成的,基于 TTL
语句中的条件。
假设我们有 events
表,并且我们希望删除所有超过一个月的记录
CREATE TABLE events
(
`event` String,
`time` DateTime,
`value` UInt64
)
ENGINE = MergeTree
ORDER BY (event, time)
TTL time + INTERVAL 1 MONTH DELETE
我们添加 TTL
语句,并使用 DELETE
操作和 time + INTERVAL 1 MONTH
。当 time
列的值超过一个月时,这将删除一条记录。
注意,记录删除是一个异步后台进程,过时记录可能在一段时间内仍然可用。
让我们尝试将一些记录(包括过时的记录)插入到表中
INSERT INTO events VALUES('error', now() - interval 2 month, 123), ('error', now(), 123);
在插入之后,我们会发现两条记录都可以在表中找到
SELECT * FROM events
┌─event─┬────────────────time─┬─value─┐
│ error │ 2022-11-24 09:34:44 │ 123 │
│ error │ 2023-01-24 09:34:44 │ 123 │
└───────┴─────────────────────┴───────┘
过时的记录将在后台通过定期安排的“非计划”合并删除。稍后
SELECT * FROM events
┌─event─┬────────────────time─┬─value─┐
│ error │ 2023-01-24 09:34:44 │ 123 │
└───────┴─────────────────────┴───────┘
管理后台删除
后台删除默认每 4 小时发生一次,可以使用 merge_with_ttl_timeout
表设置选项控制
CREATE TABLE events
...
TTL time + INTERVAL 1 MONTH DELETE
SETTINGS merge_with_ttl_timeout = 1200
此设置不要使用小于 300 秒的值,因为这会导致 I/O 负担并影响集群性能。一段时间后,我们可以发现过时的记录不再存在于我们的表中
SELECT * FROM events
┌─event─┬────────────────time─┬─value─┐
│ error │ 2023-01-24 09:34:44 │ 123 │
└───────┴─────────────────────┴───────┘
过滤要删除的行
假设我们只希望删除特定类型的记录(例如,其中 event
列值为 error
)。我们可以在 TTL
语句的 WHERE
子句中另外指定它
CREATE TABLE events
(
`event` String,
`time` DateTime,
`value` UInt64
)
ENGINE = MergeTree
ORDER BY (event, time)
TTL time + INTERVAL 1 MONTH DELETE WHERE event = 'error'
现在,只有 event='error'
的过时行将被删除
INSERT INTO events VALUES('not_error', now() - interval 2 month, 123), ('error', now(), 123)
我们可以确定 not_error
记录不会被删除(尽管它超过了一个月)
SELECT * FROM events
┌─event─────┬────────────────time─┬─value─┐
│ error │ 2023-01-24 09:48:05 │ 123 │
│ not_error │ 2022-11-24 09:48:05 │ 123 │
└───────────┴─────────────────────┴───────┘
多个删除条件
ClickHouse 允许指定多个 TTL 语句。这使我们能够更加灵活和具体地指定要删除的内容以及何时删除。假设我们想在一个月内删除所有非 error
事件,并在六个月后删除所有错误
CREATE TABLE events
(
`event` String,
`time` DateTime,
`value` UInt64
)
ENGINE = MergeTree
ORDER BY (event, time)
TTL time + INTERVAL 1 MONTH DELETE WHERE event != 'error',
time + INTERVAL 6 MONTH DELETE WHERE event = 'error'
我们可以为 TTL
语句配置任意数量的规则。
将数据移动到历史表
我们可以将 物化视图 与 TTL
语句结合使用,来处理在从主表中删除之前,我们可能希望将过时数据移动到另一个表的情况。
假设我们希望将 error
事件移动到 errors_history
表中,然后再从 events
表中删除它们。首先,我们为物化视图创建一个目标表,该表的结构与 events
表相同
CREATE TABLE errors_history (
`event` String,
`time` DateTime,
`value` UInt64
)
ENGINE = MergeTree
ORDER BY (event, time)
注意,我们不能使用 CREATE TABLE errors_history AS events
,因为这也会复制 TTL 表达式,而我们不希望发生这种情况。然后,我们创建物化视图触发器,以自动将数据导入到 errors_history
表中
CREATE MATERIALIZED VIEW errors_history_mv TO errors_history AS
SELECT * FROM events WHERE event = 'error'
现在,当我们向 events
表中插入数据时,error
事件也会自动插入到 errors_history
表中。另一方面,当 TTL
过程从 events
表中删除记录时,这些记录会保留在 errors_history
表中。
使用聚合压缩历史数据
在许多情况下,我们不希望删除数据,但我们可以降低其细节级别来节省资源。例如,考虑一种情况,我们不希望从表中删除 error
事件。同时,我们可能在一个月后不需要每秒的详细信息,因此我们可以保留每日汇总的数字。
可以使用 TTL
语句的 GROUP BY ... SET
子句来实现这一点
CREATE TABLE events
(
`event` String,
`time` DateTime,
`value` UInt64
)
ENGINE = MergeTree
ORDER BY (toDate(time), event)
TTL time + INTERVAL 1 MONTH GROUP BY toDate(time), event SET value = SUM(value)
这里有几个重要的点
GROUP BY toDate(time), event
表达式应该是 主键 的前缀,因此我们还更改了ORDER BY (toDate(time), event)
。SET value = SUM(value)
子句会将value
列设置为每个组中所有值的总和(查询中GROUP BY
子句的常见行为)。time
列值将在分组期间随机选择(与使用any()
聚合函数 的情况一样)。
确保插入以下数据
INSERT INTO events VALUES('error', now() - interval 2 month, 123),
('error', now() - interval 2 month, 321);
一段时间后,当后台合并发生时,我们可以看到我们的数据被聚合了
SELECT * FROM events
┌─event─┬────────────────time─┬─value─┐
│ error │ 2022-11-24 12:36:23 │ 444 │
└───────┴─────────────────────┴───────┘
更改压缩
虽然删除或聚合数据可能不可行,但您可能对历史数据的查询 SLA 要求更低。为了解决这个问题,我们可以考虑对较旧的数据使用更高压缩级别来节省更多空间。例如,我们可以要求 ClickHouse 对超过一个月的使用 LZ4HC
压缩的高级别数据。我们需要使用 RECOMPRESS
子句来实现这一点
CREATE TABLE events
(
`event` String,
`time` DateTime,
`value` UInt64
)
ENGINE = MergeTree
ORDER BY (toDate(time), event)
TTL time + INTERVAL 1 MONTH RECOMPRESS CODEC(LZ4HC(10))
请注意,重新压缩的数据将占用更少的空间,但压缩也需要更多时间,从而影响插入时间。
列级 TTL
我们也可以使用TTL
来管理单个列的生命周期。假设我们的表中有一个debug
列,用于存储额外的调试信息(例如,错误回溯)。此列仅在为期一周的时间内有用,在此期间它会占用大量空间。我们可以要求 ClickHouse 在一周后将其重置为默认值。
CREATE TABLE events
(
`event` String,
`time` DateTime,
`value` UInt64,
`debug` String TTL time + INTERVAL 1 WEEK
)
ENGINE = MergeTree
ORDER BY (event, time)
现在,让我们插入一条包含debug
列值的过期记录。
INSERT INTO events VALUES('error', now() - interval 1 month, 45, 'a lot of details');
一旦 TTL 处理完成,ClickHouse 将将此debug
列值重置为空字符串。
SELECT * FROM events
┌─event─┬────────────────time─┬─value─┬─debug─┐
│ error │ 2022-12-24 15:13:54 │ 45 │ │
└───────┴─────────────────────┴───────┴───────┘
请注意,ClickHouse 将使用列的默认值来设置TTL
。因此,如果存在DEFAULT
表达式,则过期的列将被分配此值。
将数据在热存储和冷存储之间移动
对于本地设置,请考虑使用存储管理来管理您的数据。通常情况下,用户需要更快的但更小的磁盘(称为热存储,例如 SSD)以及更大的但更慢的磁盘(称为冷存储,例如 HDD 或 S3)。ClickHouse 允许设置数据策略,以便在更快设备的使用率达到特定阈值时将数据移动到更慢的设备。此存储策略在两个步骤中配置 - 声明存储列表和策略。
或者,您可以考虑使用 ClickHouse Cloud,它通过使用对象存储分离存储和计算来避免这种复杂性。当与针对更常查询的数据(通常是您更新的“热”数据)的智能缓存相结合时,不再需要分层架构。
总结
ClickHouse 提供强大的数据生命周期管理工具,以实现不同存储类型之间的自动删除、压缩或移动。可以使用表级别的TTL
语句配置压缩和保留。使用磁盘策略管理存储,或者考虑使用ClickHouse Cloud作为可扩展的可靠解决方案。
有关 TTL 的更多详细信息,请参阅我们最近发布的指南。