查询越跑越卡?一文吃透 ClickHouse 自动 Merge 底层机制

一只会飞的鱼儿 2小时前 ⋅ 1 阅读
ad

ClickHouse 自动 Merge 完整详解(MergeTree 引擎核心机制)

一、什么是自动 Merge

MergeTree 系列引擎(包含 ReplacingMergeTree/AggregatingMergeTree)写入数据时不会直接合并分区内数据,每次 INSERT 都会生成独立小数据块(part); ClickHouse 后台有自动合并后台线程,定时把多个小 part 合并成大 part,实现:

  1. 减少文件数量,降低磁盘 IO、元数据查询开销
  2. 执行去重、聚合、数据压缩(Replacing/Aggregating 引擎依赖 Merge 生效逻辑)
  3. 回收过期、删除标记数据

整个过程完全后台自动执行,无需人工干预,也支持手动触发、参数调优。


二、自动 Merge 触发规则

1. 基础触发条件(默认阈值)

  1. 同一分区内存在多个 part;
  2. 满足最小合并块数量:merge_tree_min_rows_for_concurrent_merge / merge_tree_min_rows_for_active_parts
  3. 后台合并线程池有空闲资源;
  4. 避开磁盘、CPU 高负载限流。

2. 自动合并优先级

  1. 小 part 优先合并:零散几百 / 几千行的小块优先合并,防止文件爆炸;
  2. 分层合并:小块→中层块→超大块,阶梯式合并;
  3. 带删除标记、TTL 过期的 part 优先合并清理;
  4. 大分区冷数据合并会降低优先级,避免占用业务 CPU。

3. 关键:ReplacingMergeTree/AggregatingMergeTree 依赖 Merge

你业务使用的 AggregatingMergeTree(Bitmap 分群表)、ReplacingMergeTree

  • 未 Merge 前:多条同主键数据会同时存在,查询会返回多条原始数据;
  • 只有自动 Merge 完成后,才会执行聚合 / 去重逻辑,得到最终一条合并结果;
  • 临时查询想要实时去重,必须手动加 FINAL

sql

-- AggregatingMergeTree 实时聚合查询
SELECT segmentId, bitmapCardinality(user_bitmap) 
FROM segment_results FINAL 
WHERE segmentId = 'xxx';

三、查看当前合并状态(排查卡顿 / 堆积)

1. 查看全局合并任务

sql

SELECT * FROM system.merges;

字段说明:

  • database / table:执行合并的库表
  • elapsed:已耗时
  • progress:合并进度 0~1
  • rows_read/rows_written:读写行数

2. 查看表内所有数据块 part(判断是否堆积大量小块)

sql

SELECT 
    name, rows, partition, active, merge_attempts
FROM system.parts 
WHERE database = '你的库名' AND table = '你的表名'
ORDER BY rows;
  • active=1:当前可用数据块;
  • 若单分区几十上百个小 part,说明自动 Merge 跟不上写入速度,需要调参。

3. 查看合并相关系统参数

sql

SELECT name, value, comment FROM system.merge_tree_settings;

四、核心自动 Merge 调优参数(解决合并跟不上、CPU 打满)

1. 控制并发合并线程数(最常用)

ini

# 最大后台并行合并任务数,根据CPU核心调整,默认8
max_background_merges = 16
# 后台通用线程池上限(包含merge、mutation、TTL)
max_background_pool_size = 32

2. 控制触发合并的最小数据量

ini

# 单个part最少多少行才参与合并,小流量表调低,高并发大表调高
merge_tree_min_rows_for_concurrent_merge = 100000
# 单分区最小活跃part数量,超过才触发合并
merge_tree_min_rows_for_active_parts = 150000

3. 限流保护:防止合并吃光磁盘 IO/CPU

ini

# 合并时每秒最多写入字节,机械硬盘调低,SSD可拉高
merge_tree_max_write_bandwidth = 104857600
# 高负载时暂停合并(磁盘满、CPU100%)
merge_tree_stop_merges_on_high_load = 1

4. 分层合并控制(避免无限叠加超大块)

ini

# 单次合并最多合并多少个part
max_background_merge_parts = 10
# 禁止合并超过该行数的大块,防止单次合并耗时几小时
merge_tree_max_rows_to_merge = 100000000

五、手动操作 Merge(自动合并堆积时应急)

1. 手动触发指定分区全量合并

sql

OPTIMIZE TABLE 库名.表名 PARTITION '202606' FINAL;
  • FINAL:强制合并并执行聚合 / 去重(Replacing/Aggregating 必须加);
  • 适合分群 Bitmap 表,定时执行让位图提前合并,查询不加 FINAL 也能拿到完整 UV。

2. 整表全部分区合并

sql

OPTIMIZE TABLE webfunny_db_dev.segment_results FINAL;

⚠️ 高并发业务慎用,会占用大量 IO,建议低峰期执行。

3. 取消卡住的合并任务

sql

KILL MUTATION WHERE database='xxx' AND table='xxx';

六、常见自动 Merge 问题 & 解决方案

问题 1:大量小 part 堆积,合并跟不上写入

现象:system.parts 单分区几十上百个小块,查询变慢 解决:

  1. 调高 max_background_merges 增加并发合并线程;
  2. 批量写入时加大单次 insert 行数,减少小 part 生成;
  3. 低峰定时 OPTIMIZE TABLE ... FINAL 手动兜底合并。

问题 2:合并 CPU / 磁盘 IO 打满,业务查询超时

解决:

  1. 降低 merge_tree_max_write_bandwidth 限流;
  2. 开启 merge_tree_stop_merges_on_high_load 自动限流;
  3. 拆分大分区(缩短分区粒度,按天 / 按 6 小时分区)。

问题 3:AggregatingMergeTree 查询 UV 数值不准

根因:自动 Merge 未完成,多个位图块未聚合 解决:

  1. 查询统一加 FINAL
  2. 定时凌晨低峰执行 OPTIMIZE TABLE xxx FINAL 提前合并位图。

问题 4:合并任务卡死、长时间 progress 不动

  1. 磁盘满、磁盘坏道、文件权限异常;
  2. 大 part 合并内存不足,调大 merge_tree_max_memory_usage
  3. 执行 KILL MUTATION 终止卡死任务,重启 CK 服务。

七、业务落地建议(适配你的 Bitmap 分群表)

  1. 分群结果表 AggregatingMergeTree 建议按天分区分割,减少单分区合并压力;
  2. 每日凌晨定时执行 OPTIMIZE TABLE {project}_segment_results FINAL,预合并位图,提升白天报表查询速度;
  3. 监控 system.merges 任务数量、system.parts 块数量,堆积过多自动告警;
  4. 批量写入分群数据时,单次 insert 聚合更多用户,减少零散小 part 生成。

Webfunny全链路监控 + 埋点分析

一站式技术运维 和 业务分析平台,一站式大数据分析系统,我们致力于解决线上的疑难杂症和精细化分析业务数据;全链路监控系统面向技术、埋点系统面向业务,两者配合使用,相得益彰,为企业数字化业务保驾护航。

关于Webfunny

Webfunny专注于前端监控系统,前端埋点系统的研发。 致力于帮助开发者快速定位问题,帮助企业用数据驱动业务,实现业务数据的快速增长。支持H5/Web/PC前端、微信小程序、支付宝小程序、UniApp和Taro等跨平台框架。实时监控前端网页、前端数据分析、错误统计分析监控和BUG预警,第一时间报警,快速修复BUG!支持私有化部署,Docker容器化部署,可支持千万级PV的日活量!

  点赞 0   收藏 0
  • 一只会飞的鱼儿
    共发布83篇文章 获得9个收藏
全部评论: 0