ClickHouse 数据生命周期管理:TTL 自动过期与冷热分层策略
2026/7/15 21:49:21 网站建设 项目流程

ClickHouse 数据生命周期管理:TTL 自动过期与冷热分层策略

一、数据不是越多越好,是会花钱的

大家好,我是朱大喜。上个月我们团队的 ClickHouse 集群告警,磁盘使用率到了 87%。DBA 在群里发了一句:"谁的表在堆数据?能不能清一下?"

我打开一看,好家伙,有个日志表存了 3 年的明细数据,占了 4TB 空间。但实际业务查询 90% 集中在近 30 天,超过 6 个月的数据基本没人碰过。这就像你在北京三环租了个大仓库,里面塞满了三年前的旧报纸——每个月都在为它们付房租。

ClickHouse 作为一个列存分析引擎,写入快、压缩狠,但不意味着你可以无限往里塞数据。数据是有生命周期的:热数据需要存在 SSD 上保证查询速度,温数据可以挪到 HDD 降低成本,冷数据要么归档要么直接删除。ClickHouse 的 TTL(Time To Live)机制就是为这个场景量身定做的。

二、TTL 的三种玩法:删、挪、聚合

TTL 在 ClickHouse 里可以作用在三个层面:表级别列级别行级别。不同层面的 TTL 解决不同的问题。

graph TB subgraph "数据生命周期" A[数据写入<br/>当天] --> B["热数据<br/>SSD<br/>近7天"] B --> C["温数据<br/>HDD<br/>7-90天"] C --> D["冷归档<br/>对象存储<br/>90-365天"] D --> E["过期删除<br/>超过365天"] end subgraph "TTL 策略映射" F["行级TTL: 删除过期行"] --> E G["分区TTL: 移动冷热盘"] --> B G --> C H["列级TTL: 聚合历史数据"] --> D end style B fill:#FF5722,color:#fff style C fill:#FF9800,color:#fff style D fill:#2196F3,color:#fff style E fill:#9E9E9E,color:#fff

行级 TTL —— 自动删除过期数据。这是最常用也最简单的场景。比如日志表只保留 90 天,超过 90 天的数据自动清理。设置也简单:

-- 创建一个带行级TTL的表:超过90天的数据自动删除 CREATE TABLE app_logs ( event_time DateTime, -- 事件时间,TTL以这个字段为基准 user_id UInt64, event_type String, event_data String ) ENGINE = MergeTree() PARTITION BY toYYYYMMDD(event_time) -- 按天分区,方便整分区删除 ORDER BY (event_type, user_id) TTL event_time + INTERVAL 90 DAY; -- 核心:event_time超过90天,整行删除

TTL 清理不是实时触发的,而是在后台 Merge 阶段进行的。ClickHouse 会在合并数据分片时检查 TTL 条件,符合条件的行直接跳过不写入新分片。这意味着清理有延迟,但零性能代价——数据是在 Merge 过程中自然消失的,不会阻塞查询。

你还可以给 TTL 指定DELETE关键字明确表达意图,虽然默认行为就是删除:

-- 显式指定 DELETE + 按日期分区做整分区清理(性能最优) TTL event_time + INTERVAL 90 DAY DELETE WHERE event_type = 'debug' -- 只删除debug日志,关键日志保留更久

列级 TTL —— 保留行但清理列。有些场景你不想删整行,只想清理某些大字段。比如订单表里有个 JSON 格式的detail列,存了用户行为路径,这个字段 30 天后就没参考价值了,但订单的基本信息(金额、状态)需要保留。

-- 列级TTL:detail_json字段30天后自动清空,其他列保留 CREATE TABLE orders ( order_id UInt64, order_date Date, amount Decimal(10,2), status String, detail_json String -- 这个字段只保留30天 ) ENGINE = MergeTree() PARTITION BY toYYYYMM(order_date) ORDER BY order_id TTL order_date + INTERVAL 30 DAY DELETE WHERE 1=1, -- 这是行级TTL -- detail_json列在order_date+30天后被替换为默认值(空字符串) detail_json TTL order_date + INTERVAL 30 DAY;

列级 TTL 的一个妙用是对老数据做自动聚合。比如原始粒度数据保留 30 天,超过 30 天的自动聚合成小时级汇总:

-- 更优雅的做法:老数据自动聚合而不是全部丢弃 CREATE TABLE metrics_detail ( ts DateTime, metric_name String, value Float64, tags String ) ENGINE = MergeTree() PARTITION BY toYYYYMMDD(ts) ORDER BY (metric_name, ts) TTL ts + INTERVAL 30 DAY GROUP BY toStartOfHour(ts) AS ts, -- 聚合到小时粒度 metric_name, sum(value) AS value, '' AS tags -- tags字段在聚合后丢弃 SETTINGS materialize_ttl_after_modify = 0;

三、冷热分层:SSD 和 HDD 搭配使用

如果你的 ClickHouse 节点既有 SSD 又有 HDD(最常见的配置是系统盘 SSD + 数据盘 HDD 组 RAID),可以使用存储策略来实现冷热分层。热数据放 SSD 上保证查询性能,冷数据自动迁到 HDD 上节省成本。

-- 第一步:在 config.xml 中定义存储策略(DBA操作) -- <storage_configuration> -- <disks> -- <ssd> -- <path>/data/ssd/clickhouse/</path> -- </ssd> -- <hdd> -- <path>/data/hdd/clickhouse/</path> -- </hdd> -- </disks> -- <policies> -- <hot_cold> -- <volumes> -- <hot> -- <disk>ssd</disk> -- <max_data_part_size_bytes>107374182400</max_data_part_size_bytes> -- </hot> -- <cold> -- <disk>hdd</disk> -- </cold> -- </volumes> -- </hot_cold> -- </policies> -- </storage_configuration> -- 第二步:建表时指定存储策略 + TTL自动迁移 CREATE TABLE events ( event_time DateTime, user_id UInt64, event_type LowCardinality(String), -- LowCardinality 降低基数类型的存储 properties Map(String, String) -- Map类型适合存动态属性 ) ENGINE = MergeTree() PARTITION BY toYYYYMM(event_time) ORDER BY (event_type, user_id) TTL event_time + INTERVAL 7 DAY TO VOLUME 'hot', -- 7天内在SSD event_time + INTERVAL 90 DAY TO VOLUME 'cold'; -- 7-90天迁移到HDD

这里TO VOLUME指令的神奇之处在于:它不是复制数据,而是物理上将数据分片从 SSD 目录移动到 HDD 目录。这个迁移同样发生在后台 Merge 过程中,不影响查询。

冷热分层的成本收益非常可观。以一个 10 节点集群为例,如果全部用 SSD 存储,每节点配 4TB SSD,月成本很高。用冷热分层,每节点配 1TB SSD + 10TB HDD,SSD 只放近 7 天热数据,90% 以上的数据落在 HDD 上,总存储成本能降低 60% 以上,而查询性能只在对冷数据做全量扫描时才受到影响——而这种查询本来就不多见。

四、TTL 使用中的三个避坑指南

坑一:TTL 不是删分区,是删行。很多同学以为设置了TTL event_time + INTERVAL 90 DAY就万事大吉了,ClickHouse 会自动把 90 天前的分区整个删掉。不是的——TTL 是按行判断的,而且依赖 Merge 过程。如果你的表写入量不大、Merge 不频繁,过期数据可能存留很长时间才被清理。如果想立即释放空间,手动ALTER TABLE ... DROP PARTITION才是正解。

坑二:TTL 表达式里只能用表里的列。你不能写TTL now() - INTERVAL 90 DAY,因为now()不是表的列。必须以表中的时间列为基准。这其实是个好设计——防止你不小心写错导致整表数据被清。

坑三:列级 TTL 清空后数据并没有真的从磁盘消失。ClickHouse 的列级 TTL 是把列值替换为默认值(通常是空字符串或 0),物理上这些空值仍然占据压缩存储空间。但好消息是,ClickHouse 的列存压缩对空值的处理非常高效——连续的空值压缩到几乎为零。所以空间释放效果虽然不如行级 TTL 直接,但也足以满足大部分场景。

-- 手动触发TTL清理(在业务低峰期执行) ALTER TABLE app_logs MATERIALIZE TTL; -- 查看TTL相关信息,确认清理前的数据量 SELECT partition, sum(rows) AS rows, formatReadableSize(sum(bytes_on_disk)) AS size, min(event_time) AS min_time, max(event_time) AS max_time FROM system.parts WHERE table = 'app_logs' AND active = 1 GROUP BY partition ORDER BY partition DESC;

五、总结

ClickHouse 的 TTL 机制是数据生命周期管理的利器,但不同层面的 TTL 有各自的适用场景:行级 TTL 适合明确有"丢弃时间"的日志型数据;列级 TTL 适合大字段的成本优化;存储策略 TTL 适合冷热分层的成本控制。

落地时的优先顺序建议:先上行级 TTL(解决磁盘被打爆的亡羊补牢问题),再上存储策略(冷热分层降成本),最后考虑列级 TTL 和聚合 TTL(精细优化)。按照这个顺序逐步推进,每一步都能看到立竿见影的效果。

最后一句话:磁盘空间不是大风刮来的——无论是物理机上的 SSD 还是云上的 EBS,都在按 GB 计费。让你的 ClickHouse 只存真正有价值的数据,而不是当个垃圾桶什么都往里塞。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询