简介
如果您在 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
事件,并在 6 个月后删除所有 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
语句来解决我们可能希望在从主表删除过时数据之前将其移动到另一个表的情况。
假设我们想在从 events
表中删除 error
事件之前,将其移动到 errors_history
表。首先,我们为物化视图创建一个目标表,该表的结构与 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 的更多详细信息,请参阅我们最近发布的指南。