博客 / 工程

使用 TTL 管理 ClickHouse 中的数据生命周期

author avatar
Denys Golotiuk
2023年1月31日 - 10 分钟阅读

Data Lifecycles.png

简介

如果您在 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:44123 │
│ error │ 2023-01-24 09:34:44123 │
└───────┴─────────────────────┴───────┘

过时的记录将在后台通过定期调度的“计划外”合并删除。稍后

SELECT * FROM events
┌─event─┬────────────────time─┬─value─┐
│ error │ 2023-01-24 09:34:44123 │
└───────┴─────────────────────┴───────┘

管理后台删除

默认情况下,后台删除每 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:44123 │
└───────┴─────────────────────┴───────┘

筛选要删除的行

假设我们只想删除特定类型的记录(例如,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:05123 │
│ not_error │ 2022-11-24 09:48:05123 │
└───────────┴─────────────────────┴───────┘

多个删除条件

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:5445 │       │
└───────┴─────────────────────┴───────┴───────┘

请注意,ClickHouse 将对列 TTL 使用默认值。因此,如果存在 DEFAULT 表达式,则过时的列将被分配此值。

在热存储和冷存储之间移动数据

对于本地设置,请考虑对数据使用存储管理。通常,用户需要更快但更小的磁盘(称为热存储,例如 SSD)和更大但更慢的磁盘(称为冷存储,例如 HDD 或 S3)。ClickHouse 允许设置数据策略,以便在较快设备的利用率达到特定阈值后,将数据移动到较慢的设备。此存储策略分两步配置 - 声明存储列表和策略。

或者,考虑使用 ClickHouse Cloud,它通过使用对象存储分离存储和计算来避免这种复杂性。当与更频繁查询的数据(通常是您较新的“热”数据)的智能缓存结合使用时,不再需要分层架构。

总结

ClickHouse 提供了强大的数据生命周期管理工具,以实现自动删除、压缩或在不同存储类型之间移动。可以使用表级别的 TTL 语句配置压缩和保留。使用磁盘策略管理存储,或考虑使用 ClickHouse Cloud 作为可靠的扩展解决方案。

有关 TTL 的更多详细信息,请参阅我们最近发布的指南

分享这篇文章

订阅我们的新闻通讯

及时了解功能发布、产品路线图、支持和云产品信息!
正在加载表单...
关注我们
X imageSlack imageGitHub image
Telegram imageMeetup imageRss image