Clickhouse可以设置每个表的存储策略polices,存储策略中定义了卷volumes,每个卷volumes又可以使用定义好的磁盘disk。然后利用表数据的TTL设置功能,将达到TTL条件的数据存储迁移至存储策略下的某个卷中。

配置冷数据写入OSS#

修改配置文件,定义存储策略,卷和磁盘#

编辑 /etc/clickhouse-server/config.xml 或 /etc/clickhouse-server/config.d/oss_storage.xml,在 <clickhouse> 标签内添加 <storage_configuration>标签。 OSS 使用 S3 兼容接口,ClickHouse 从 21.8 版本开始对 s3 磁盘支持就比较稳定了,建议使用 22.x 及以上版本。 CLickhouse中存储有Volume和Disk的概念。Volume关联实际的Disk,然后配置表格将数据写到Volume中。

<clickhouse>
    <storage_configuration>
        <disks>
            <!-- 原有的本地磁盘(默认存在,可不用显式声明) -->
            <default>
                <path>/var/lib/clickhouse/</path>
            </default>
            <!-- 新增:OSS 冷数据磁盘 -->
            <oss_cold>
                <type>s3</type>
                <endpoint>https://oss-cn-shanghai.aliyuncs.com</endpoint>
                <access_key_id>你的AK</access_key_id>
                <secret_access_key>你的SK</secret_access_key>
                <region>oss-cn-shanghai</region>
                <!-- 建议为每个集群节点设置不同的根路径,避免冲突 -->
                <metadata_path>/var/lib/clickhouse/disks/oss_cold/</metadata_path>
            </oss_cold>
        </disks>
        <policies>
            <!-- 冷热分层策略 -->
            <hot_cold_oss>
                <volumes>
                    <!-- 热数据卷:本地 SSD -->
                    <hot>
                        <disk>default</disk>
                    </hot>
                    <!-- 冷数据卷:OSS -->
                    <cold>
                        <disk>oss_cold</disk>
                    </cold>
                </volumes>
            </hot_cold_oss>
        </policies>
    </storage_configuration>
</clickhouse>

说明:

  • endpoint 填写 OSS 的区域域名(内网域名可免流量费,但需保证网络相通)。
  • metadata_path 用于存放本地元数据,必须为每个 s3 磁盘指定一个唯一的本地路径。
  • 如果使用 ECS 的 RAM 角色,可设置 <use_environment_credentials>true</use_environment_credentials> 并去掉 AK/SK。
  • 保存后重启 ClickHouse 使配置生效。

设置表属性并关联存储策略#

新建表并开启冷热分层#

建表时通过 SETTINGS storage_policy = 'hot_cold_oss' 使用刚定义的策略,并用 TTL 指定冷数据自动迁移的时间。

CREATE TABLE test.oss_cold_table
(
    `event_time` DateTime,
    `user_id`    UInt32,
    `event`      String
)
ENGINE = MergeTree
PARTITION BY toYYYYMM(event_time)         -- 按月分区
ORDER BY (user_id, event_time)
TTL event_time + INTERVAL 30 DAY TO VOLUME 'cold'   -- 30天前的数据自动转到 cold 卷(即 OSS)
SETTINGS storage_policy = 'hot_cold_oss';
  • TO VOLUME 'cold' 表示数据移动到策略里定义的 cold 卷(我们配置的是 OSS 盘)。
  • 也可以直接 TO DISK 'oss_cold',但推荐用卷(volume)以保持灵活性。
  • TTL 基于分区键的最小值/最大值触发移动,因此分区粒度要合理(例如按月分区,按月移动)。 如果表已经存在,可以用 ALTER 追加 TTL:
ALTER TABLE test.oss_cold_table MODIFY TTL event_time + INTERVAL 30 DAY TO VOLUME 'cold';

已有表开启冷热分层#

1. 确认 ClickHouse 版本(需 ≥ 22.3)#
SELECT version();

需要支持 ALTER TABLE ... MODIFY SETTING storage_policy,否则只能重建表。

2. 将表关联到已配置的存储策略#
  1. 关联存储策略
ALTER TABLE logs.alert_event_details_xjjc MODIFY SETTING storage_policy = 'hot_cold_oss';

这步让表知道自己有冷热两个卷可用。 2. 修改 TTL,增加 TO VOLUME 规则

ALTER TABLE logs.alert_event_details_xjjc
   MODIFY TTL
       toDateTime(insert_time) + toIntervalDay(30) TO VOLUME 'cold',
       toDateTime(insert_time) + toIntervalDay(180) DELETE;

TTL 会按顺序执行:先移动,后删除。 必须先关联存储策略,再增加TO VOLUME规则。这是因为CLickhouse中Volume(卷)不是全局独立的,它必须“居住”在一个 Storage Policy(存储策略)里。表只有通过绑定策略,才能“看见”并“使用”其中的卷。当表执行 TTL ... TO VOLUME 'cold' 时,数据库会先查看这张表绑定的策略是什么,然后在该策略内查找名为 cold 的卷。

注意事项#

  • S3 磁盘上的数据是只读的:ClickHouse 不会在 OSS 上原地更新或合并数据。当本地热盘上的分区需要与冷盘分区合并时,会把 OSS 上的数据暂时下载到本地,合并后再上传回去,可能会产生额外的请求费用和延迟。
  • 不要频繁修改 OSS 分区:尽量设计好分区键,让冷数据一次转入后很少变动。
  • 副本表需统一配置:如果使用 ReplicatedMergeTree,需要所有副本的 config.xml 都包含相同的 OSS 磁盘和策略定义,否则复制的 MOVE PARTITION 操作会失败。
  • 网络与权限:确保 ClickHouse 节点能访问 OSS 端点,并且 AK/SK 有读写目标 Bucket 的权限。
  • 性能考虑:查询大量位于 OSS 上的数据会比本地磁盘慢(网络延迟),适合不常访问的归档场景。 通过以上配置,你的 ClickHouse 冷数据就可以按时间规则自动、透明地沉降到阿里云 OSS 中,既节省本地存储成本,又不影响查询能力。

验证冷数据自动迁移#

插入一些模拟数据,手动调一下时间,再触发分区合并,观察数据分布。

-- 插入一些“旧”数据,距今超过30天
INSERT INTO test.oss_cold_table
SELECT now() - INTERVAL 60 DAY, number, 'old_event'
FROM numbers(1000);
-- 强制触发分区合并与 TTL 移动(也可等后台自动执行) OPTIMIZE TABLE test.oss_cold_table FINAL; -- 查看每个分区所在的磁盘 SELECT
    partition,
    name,
    disk_name,
    active
FROM system.parts
WHERE table = 'oss_cold_table' AND database = 'test'
ORDER BY partition;

你应该能看到,30天以前的分区对应的 disk_name 会变成 oss_cold,而近期的数据仍在 default 磁盘上。

检查表上的存储策略#

查看集群名称#

SELECT
    cluster,
    shard_num,
    shard_weight,
    replica_num,
    host_name,
    port,
    is_local
FROM system.clusters
ORDER BY cluster, shard_num, replica_num;

查看存储策略与卷的层次结构#

SELECT
    policy_name,
    volume_name,
    volume_priority,
    disk_name,
    max_data_part_size,   -- 卷级别可选的参数
    move_factor
FROM system.storage_policies
ORDER BY policy_name, volume_priority, disk_name;

system.storage_policies 表在较新版本(24.x / 25.x)中已经去掉了 disk_name 列,改用 disks 数组。

SELECT
    policy_name,
    volume_name,
    volume_priority,
    disk_array AS disk_name,
    max_data_part_size,
    move_factor
FROM system.storage_policies
ARRAY JOIN disks AS disk_array
ORDER BY policy_name, volume_priority, disk_name;

查看磁盘#

SELECT
    name,
    path,
    type,           -- 磁盘类型(local、s3、cache,objectstorage等a)
    free_space,
    total_space
FROM system.disks;

产看表的存储策略#

检查log数据库中的alert_event_details_xjjc表应用的存储策略。

SELECT
    database,
    table,
    storage_policy,
    create_table_query
FROM system.tables
WHERE database = 'logs' AND table = 'alert_event_details_xjjc';

从OSS中查询数据#

CLickhouse会根据查询的数据自动从OSS中拉取数据并解析,无需手动加载。对用户来说,感觉和查本地数据一样,无需任何特殊语法。

1. 查询时,ClickHouse 自动从 OSS 读数据#

你对表执行一条 SQL:

SELECT user_id, event_time, event
FROM test.oss_cold_table
WHERE event_time < '2025-01-01';

ClickHouse 内部流程:

  • 解析 SQL,确定需要扫描哪些分区/part。
  • 通过 system.parts 发现部分 part 所在磁盘为 oss_cold。
  • 如果本地缓存中没有,自动从 OSS 下载该 part 的二进制数据(如 marks 文件、columns 数据块)到本地临时存储(通常是 disk 的 cache 或 /tmp)。
  • 在本地内存/临时文件中完成查询计算,返回结果。
  • 临时数据随后可能会被清理,也可配置缓存来保留。

2. 如何确认数据真的在 OSS 上且可查#

用 system.parts 查看分区对应的磁盘:

SELECT
    partition,
    name,
    disk_name,
    active,
    rows
FROM system.parts
WHERE table = 'oss_cold_table' AND database = 'test'
ORDER BY partition DESC;

你会看到类似结果:

┌─partition─┬─name───────┬─disk_name─┬─active─┬─rows─┐
│ 202507    │ 202507_1_1 │ oss_cold  │      1 │ 500  │
│ 202508    │ 202508_2_2 │ default   │      1 │ 300  │
└───────────┴────────────┴───────────┴────────┴──────┘
  • disk_name = 'oss_cold' 的分区就是已经搬迁到 OSS 上的数据。
  • 直接查询这些分区,依然可以正常返回结果。

3. 性能优化:减少每次查 OSS 的开销#

因为 OSS 有网络延迟,如果频繁查询同一份冷数据,可配置本地 SSD 缓存,把热访问的 OSS 数据块缓存在本地。 在 storage_configuration 里定义一块 cache 磁盘,叠加在 s3 磁盘上:

<disks>
    <oss_cold>
        <type>s3</type>
        <!-- ... 你的 OSS 配置 ... -->
    </oss_cold>
    <!-- 给 OSS 盘加本地缓存 -->
    <oss_cold_cache>
        <type>cache</type>
        <disk>oss_cold</disk>          <!-- 实际后端是 OSS -->
        <path>/var/lib/clickhouse/disks/oss_cache/</path>
        <max_size>200Gi</max_size>     <!-- 本地缓存大小 -->
        <cache_on_write>false</cache_on_write>
    </oss_cold_cache>
</disks>

然后把存储策略的 cold 卷指向 oss_cold_cache 磁盘即可。
之后查询 OSS 数据时,首次读取会从 OSS 下载并写入本地缓存,后续读取命中缓存直接本地访问,速度会快很多。

4. 查 OSS 数据的注意事项#

  • 数据只读:OSS 上的 part 是只读的,不能直接更新,只能被查询或重新合并。
  • 网络依赖:如果 OSS 不可达(网络断了、AK/SK 过期),对冷数据的查询会报错。
  • 不要手动删除 OSS 文件:OSS 上的文件结构由 ClickHouse 管理,外部删除会导致查询失败或数据丢失。
  • 查询尽量带分区键过滤:WHERE event_time >= ... 可以大大减少需要扫描的 OSS part 数量,降低网络 IO。

5. 如果需要把数据从 OSS 迁回本地#

如果业务变了,想把某些冷分区重新变成热数据(移回本地 default 磁盘),用 ALTER TABLE ... MOVE PARTITION 命令:

ALTER TABLE test.oss_cold_table
    MOVE PARTITION '202507' TO DISK 'default';

这样该分区就会从 OSS 下载全部文件并移动到本地磁盘,查询又变回全本地速度。

云厂商托管CLickhouse的数据冷热存储#

利用云厂商自己的冷热存储,无需配置AK/SK。

第一步:确认表已开启冷热分层#

如果 normalized_logs 表还没设置冷热存储策略,先执行:

ALTER TABLE your_database.normalized_logs 
    MODIFY SETTING storagepolicy = 'hottocold';

这步只需执行一次,开启后数据写入时就会按策略管理。

第二步:设置基于时间的 TTL 迁移规则#

假设你的表有一个时间列(例如 event_time 或 log_date),执行以下语句让超过20天的数据自动迁移到 COS:

ALTER TABLE your_database.normalized_logs 
    MODIFY TTL event_time + INTERVAL 20 DAY TO VOLUME 'cold';
  • event_time:替换为你的表实际的时间列名
  • INTERVAL 20 DAY:超过20天的数据将自动迁移
  • TO VOLUME 'cold':指定迁移到冷存储卷(COS)

第三步:验证 TTL 是否生效#

执行以下查询查看 TTL 定义:

SELECT 
    table,
    engine,
    ttl_expression,
    storage_policy
FROM system.tables
WHERE database = 'your_database' AND table = 'normalized_logs';

再通过 system.parts 确认迁移进度:

SELECT 
    partition,
    disk_name,
    rows,
    min_time,
    max_time
FROM system.parts
WHERE active = 1 AND database = 'your_database' AND table = 'normalized_logs'
ORDER BY min_time;

当 disk_name 显示为 ObjectStorage 时,说明该分区已成功迁移到 COS

Clickhouse底层数据迁移机制#

如果修改了表的TTL规则或存储策略,并不会让老Part自动迁移。只有必须满足以下所有条件时,后台 TTL 任务才会自动移动老 part:

  • 表已有 TTL 规则且包含 TO VOLUME / TO DISK;
  • 老 part 的 TTL 信息已经是最新的(即修改 TTL 后执行过 MATERIALIZE TTL,或 part 是修改后新生成的);
  • part 满足 TTL 条件(如日期超过 6 个月); 否则,老 part 会一直留在原处。 这是因为当为表设置了ttl之后,该表新创建的part会将这个ttl保存至ttl.txt文档中。在CLickhouse的每个表都有一个后台任务,会定期的扫描这些part的时间,确认了某个part到期了之后,就开始移动。这也是为什么修改了表的ttl之后,在修改ttl之前创建part的ttl不会跟着被修改的原因。这和 DELETE TTL 不一样。DELETE TTL 在 TTL merge 时是会逐行判断的 —— 重写 part、丢掉过期行、留下没过期的。那才会产生"拆分"效果,代价是重写数据。

1. 修改的是 TTL 规则(新增/修改 TO VOLUME / TO DISK)#

ALTER TABLE db.table MODIFY TTL date + INTERVAL 6 MONTH TO VOLUME 'cold';
  • 仅修改元数据:已存在的 part 不会立即重新计算 TTL,它们仍保留旧的 TTL 信息。
  • 后台 TTL 任务不会自动移动它们,除非 part 参与合并并生成新 part,或者你手动执行 MATERIALIZE TTL。
  • 要让老 part 立即应用新 TTL 并移动,需执行:
ALTER TABLE db.table MATERIALIZE TTL;

该操作会遍历所有 part,重新计算 TTL,并触发满足条件的 part 移动到目标卷(或删除过期数据)。
⚠️ 注意:MATERIALIZE TTL 是重操作,会重写 part,消耗大量 IO/CPU,建议在低峰期执行。

2. 修改的是 storage_policy 设置#

ALTER TABLE db.table MODIFY SETTING storage_policy = 'tiered';
  • 这只影响新写入的数据和后续合并产生的 part。
  • 已有老 part 不会自动迁移到新策略的卷。
  • 它们会留在原来的磁盘/卷上,直到:
    • 发生合并,且合并后的 part 根据新策略写入新卷(但通常合并会留在原卷,除非有 TTL 移动规则);
    • 或者你手动执行 ALTER TABLE ... MOVE PARTITION ... TO VOLUME ...

所有命中 TTL 的历史月份分区都会被排进后台移动队列,每个分区都要从本地盘读、写到 TOS,会持续占用冷存带宽 + 本地盘读 IO + 网络。数据量大时这个过程可能持续数小时到数天,期间对在线查询 / 写入有一定挤压。

大表的迁移#

  1. 先暂停后台移动
SYSTEM STOP MOVES;
  1. 手动逐月迁移分区
ALTER TABLE db.table MOVE PARTITION '2023-01' TO VOLUME 'cold';
ALTER TABLE db.table MOVE PARTITION '2023-02' TO VOLUME 'cold';
-- 逐月执行,观察影响
  1. 调整并发参数
    • 降低 background_move_pool_size,减少同时上传的 part 数。
    • 限制 OSS 连接数,如 s3_max_connections。
  2. 在低峰期操作
    • 避免影响在线查询和写入。
  3. 监控进度
SELECT * FROM system.moves;
SELECT partition, name, disk_name, volume_name FROM system.parts WHERE active;
  1. 迁移完成后再启动 TTL 自动管理
SYSTEM START MOVES;

参考文档#

  1. xx

外链#