DoubleCloud 即将停止运营。使用限时免费迁移服务迁移到 ClickHouse。立即联系我们 ->->

博客 / 工程

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

author avatar
Denys Golotiuk
2023 年 1 月 31 日

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 事件,并在六个月后删除所有错误

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

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

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

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

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

总结

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

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

分享此文章

订阅我们的时事通讯

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