1. ClickHouse磁盘空间管理的核心挑战

第一次用ClickHouse存日志数据时,我盯着服务器监控面板上不断飙升的磁盘使用率直冒冷汗——明明上周刚加了2TB存储,怎么又快满了?这种经历相信很多用ClickHouse做时序数据存储的朋友都遇到过。与传统数据库不同,ClickHouse独特的存储机制在带来极致查询性能的同时,也带来了特殊的空间管理难题。

ClickHouse的数据写入方式就像往仓库里堆放货物。每次INSERT操作都会生成新的"数据块"(part),修改数据时不是原地更新,而是打上删除标记后写入新版本。这种机制类似于快递仓库的出入库记录:新到货品直接堆放(INSERT),退货商品只是贴上红标签(DELETE),实际货架位置不变。日积月累后,仓库里堆满了带着红标签的"僵尸货物",这就是磁盘空间被无效占用的根本原因。

更麻烦的是系统日志表。默认安装的ClickHouse会创建trace_log、query_log等6个系统日志表,它们就像24小时运转的监控摄像头,不断记录着数据库的每个操作。我曾遇到一个客户案例,系统日志三个月就吃掉了500GB空间,而业务数据实际才占用200GB。这些日志表往往被开发者忽视,直到收到磁盘告警才手忙脚乱地处理。

2. 手动清理的实战操作指南

2.1 精准定位空间占用元凶

当收到磁盘告警时,我首先会运行这个空间分析SQL:

SELECT 
    table,
    formatReadableSize(sum(bytes)) AS total_size,
    count() AS parts_count,
    max(modification_time) AS latest_modified
FROM system.parts 
WHERE active
GROUP BY table
ORDER BY total_size DESC
LIMIT 10;

这个查询会列出空间占用TOP10的表,并显示每个表包含的数据块数量。上周在某电商平台优化时,发现他们的trace_log表竟然有1200个数据块,而正常情况应该不超过50个,这就是典型的合并操作滞后的表现。

对于单个表的详细分析,我常用这个"数据块CT扫描"语句:

SELECT 
    partition,
    name,
    rows,
    formatReadableSize(bytes_on_disk) AS size,
    modification_time
FROM system.parts 
WHERE table = 'trace_log'
ORDER BY bytes_on_disk DESC
LIMIT 20;

2.2 安全删除数据的正确姿势

删除数据不是简单执行DELETE语句就完事了。我有次在生产环境直接运行ALTER TABLE DELETE,结果引发长达2小时的IO阻塞。现在我会采用分批次删除策略:

-- 分批删除30天前的trace_log(每次处理1天数据)
SET max_block_size = 1000000;
ALTER TABLE system.trace_log 
DELETE WHERE event_date < today() - 30 
AND event_date >= toDate('2023-01-01');

对于关键业务表,删除前务必先备份:

clickhouse-client --query="SELECT * FROM orders WHERE date < '2023-01-01'" > old_orders_backup.tsv

2.3 手动合并的隐藏技巧

常规的OPTIMIZE TABLE FINAL会全表锁定,在亿级数据表上可能耗时数小时。我总结出两个优化技巧:

  1. 按分区合并(需要表有分区键):
OPTIMIZE TABLE trace_log PARTITION '202301' FINAL;
  1. 使用后台合并模式(ClickHouse 21.6+):
OPTIMIZE TABLE trace_log FINAL DEDUPLICATE SETTINGS optimize_throw_if_noop=0;

合并完成后,一定要验证效果:

SELECT
    table,
    formatReadableSize(sum(bytes)) AS before,
    formatReadableSize(sum(bytes_on_disk)) AS after
FROM system.parts
WHERE table = 'trace_log';

3. 自动化维护方案设计

3.1 智能合并参数配置

在表创建时预置优化参数,比事后补救更有效。这是我的标准模板:

CREATE TABLE event_log (
    event_date Date,
    user_id UInt64,
    event_type String
) ENGINE = MergeTree()
PARTITION BY toYYYYMM(event_date)
ORDER BY (event_date, user_id)
SETTINGS 
    merge_with_ttl_timeout = 86400,
    min_bytes_for_wide_part = 10737418240,
    non_replicated_deduplication_window = 1000;

关键参数说明:

  • merge_with_ttl_timeout:设置后台合并的触发间隔
  • min_bytes_for_wide_part:10GB以上数据块启用特殊存储格式
  • non_replicated_deduplication_window:控制内存中重复数据检测范围

3.2 系统日志的自动化管理

通过修改config.xml实现日志自动化清理:

<yandex>
    <asynchronous_metric_log>
        <ttl>2592000</ttl>  <!-- 30天保留期 -->
        <flush_interval_milliseconds>7500</flush_interval_milliseconds>
    </asynchronous_metric_log>
    <trace_log>
        <engine>
            ENGINE = MergeTree()
            TTL event_date + INTERVAL 1 MONTH
            SETTINGS merge_with_ttl_timeout = 3600
        </engine>
    </trace_log>
</yandex>

3.3 监控告警体系搭建

我用这套Prometheus+Grafana监控方案:

  1. 配置ClickHouse的Prometheus端点
<yandex>
    <prometheus>
        <endpoint>/metrics</endpoint>
        <port>9363</port>
    </prometheus>
</yandex>
  1. 关键监控指标:
  • clickhouse_disk_space_used{disk='default'}
  • clickhouse_table_parts_count
  • clickhouse_background_pool_task
  1. 设置智能告警规则:
groups:
- name: clickhouse-space-alert
  rules:
  - alert: DiskSpaceCritical
    expr: clickhouse_disk_space_used / clickhouse_disk_space_total > 0.85
    for: 15m
    labels:
      severity: critical
    annotations:
      summary: "ClickHouse disk space critical ({{ $value }}%)"

4. 高级优化技巧与避坑指南

4.1 冷热数据分层存储

对于历史数据采用COLD存储策略:

ALTER TABLE event_log MODIFY SETTING 
    storage_policy = 'hot_cold',
    disks = ['hot_ssd', 'cold_hdd'],
    volumes = [
        {
            'name': 'hot',
            'disks': ['hot_ssd'],
            'max_data_part_size_bytes': '10737418240'
        },
        {
            'name': 'cold',
            'disks': ['cold_hdd'],
            'prefer_not_to_merge': true
        }
    ];

4.2 数据压缩算法选型

不同数据类型适用不同压缩算法:

ALTER TABLE logs MODIFY COLUMN 
    json_data String CODEC(ZSTD(3)),
    ip_address FixedString(16) CODEC(LZ4),
    timestamp DateTime64 CODEC(DoubleDelta);

实测效果对比:

数据类型原始大小LZ4压缩ZSTD压缩
JSON日志10GB3.2GB2.1GB
IP地址数据4GB1.8GB1.7GB
时间戳序列2GB1.5GB0.9GB

4.3 常见问题解决方案

问题1:OPTIMIZE卡住不动

  • 检查system.merges表确认合并进度
  • 调整后台合并线程数:SET max_background_merges = 8;

问题2:删除数据后空间不释放

  • 确认使用ALTER TABLE DELETE而非TRUNCATE
  • 检查文件系统是否支持稀疏文件

问题3:频繁出现"Too many parts"错误

  • 优化写入批次大小
  • 调整parts_to_delay_insertparts_to_throw_insert参数

在金融行业某客户的实际案例中,通过组合应用上述技术,将500TB集群的存储成本降低了63%,年节省云存储费用超$200万。关键点在于建立完整的空间管理闭环:从实时监控、智能合并到分级存储,形成可持续的优化机制。

更多推荐