☰
ClickHouse Schema修改引发的Merge阻塞问题解析
2026/11/3 7:02:51 网站建设 项目流程

1. ClickHouse Schema修改引发的Merge阻塞问题深度解析

作为一名长期与ClickHouse打交道的工程师,我最近遇到一个由Schema修改引发的Merge阻塞问题,整个过程堪称教科书级的分布式系统故障排查案例。本文将详细记录从问题发现到最终解决的完整过程,并深入分析背后的技术原理。

1.1 问题背景与现象

我们在生产环境中对emo_mdm_flowwork_pt1m_local表执行了添加5个String类型列的DDL操作:

ALTER TABLE default.emo_mdm_flowwork_pt1m_local ON CLUSTER cluster ADD COLUMN IF NOT EXISTS `host` String DEFAULT '' AFTER connType, ADD COLUMN IF NOT EXISTS `path` String DEFAULT '' AFTER host, ADD COLUMN IF NOT EXISTS `referrer` String DEFAULT '' AFTER path, ADD COLUMN IF NOT EXISTS `title` String DEFAULT '' AFTER referrer, ADD COLUMN IF NOT EXISTS `referrerHost` String DEFAULT '' AFTER title;

操作看似简单,却引发了连锁反应:

  1. Mutation阻塞:数据删除操作在部分分片上永久阻塞
  2. Merge失败:后台Merge任务大量失败,错误日志显示类型转换异常
  3. 查询异常:涉及新增列的查询会触发相同错误

1.2 初步排查与应急处理

1.2.1 资源池调整无效

我们首先怀疑是MergeAndMutatePool资源不足导致。将background_pool_size从32调整到48后:

# 监控指标变化 ClickHouseMetrics_BackgroundMergesAndMutationsPoolSize: 64 → 96

虽然部分分片恢复,但仍有分片持续阻塞,说明不是根本原因。

1.2.2 错误日志分析

关键错误信息反复出现:

Bad cast from type DB::ColumnNothing to DB::ColumnLowCardinality while reading from part /path/to/part

堆栈显示问题发生在VerticalMergeStage,且与复杂类型处理相关。特别值得注意的是:

  • 报错的不是新增的5个String列
  • 问题只出现在添加列之前创建的part中
  • 涉及LowCardinality类型的复合列(Map/Array/Tuple)

2. 深入诊断过程

2.1 问题复现条件验证

通过以下查询验证问题触发条件:

-- 不包含新增列的查询 → 正常 SELECT count(1) FROM table WHERE timestampMs BETWEEN... AND flowId IN(...) -- 包含新增列的查询 → 报错 SELECT count(1) FROM table WHERE timestampMs BETWEEN... AND referrer = 'value'
2.2 Part元数据分析

对比新旧part的列存储情况:

# 新part包含新增列 ls /path/to/new_part | grep referrer referrer.bin referrer.cmrk2 ... # 旧part缺少新增列 ls /path/to/old_part | grep referrer [无输出]
2.3 系统表查询

通过系统表定位受影响范围:

-- 查找所有报错的part SELECT database, table, partition_id FROM system.parts WHERE name IN ( SELECT DISTINCT result_part_name FROM system.merges WHERE last_exception LIKE '%Bad cast%' )

结果显示问题集中在emo_mdm_flowwork_*系列表,且只影响添加列前创建的part。

3. 根本原因分析

3.1 ClickHouse的Merge机制

当执行Merge操作时,ClickHouse会:

  1. 确定Merge算法(Horizontal/Vertical)
  2. 读取源part数据
  3. 处理缺失列(新增列场景)
  4. 写入新part

在Vertical Merge模式下,列是按需读取的,这触发了缺失列的默认值填充逻辑。

3.2 默认值填充的两阶段过程

当读取的part缺少表中存在的列时:

  1. 第一次补列:fillMissingColumns()

    • 为简单类型填充类型默认值
    • 为复合类型准备offset信息
    // 示例:为Map类型准备默认数据结构 ColumnPtr column = data_type->createColumn(); column->insertDefault();
  2. 第二次补列:evaluateMissingDefaults()

    • 计算表达式默认值
    • 此处发生了ColumnNothing到ColumnLowCardinality的错误转换
3.3 复合类型的Substream处理

复杂类型的列(如Map(LowCardinality(String), String))通过Substream机制处理:

  1. SubstreamPath:标识嵌套结构中的具体子列
  2. Serialization:各类型实现自己的序列化逻辑
    • SerializationMap
    • SerializationArray
    • SerializationTuple

当处理缺失的复合列时,类型系统未能正确处理LowCardinality子列的默认值构造。

4. 解决方案与验证

4.1 临时解决方案

强制使用Horizontal Merge算法:

SET vertical_merge_algorithm_min_rows_to_activate = 2000000000; SET vertical_merge_algorithm_min_bytes_to_activate = '536870912000';

然后执行OPTIMIZE:

OPTIMIZE TABLE emo_mdm_flowwork_pt1m_local PARTITION '20250925' FINAL;
4.2 根本解决

通过代码分析发现问题根源:

  1. 缺失列处理时未正确维护LowCardinality类型信息
  2. 复合类型的Substream路径处理存在边界条件缺陷

添加调试日志确认问题:

LOG_DEBUG(log, "Missing column: {}, type: {}", column.name, column.type->getName());

最终通过以下方式彻底解决:

  1. 升级到修复该问题的ClickHouse版本
  2. 对历史part执行强制重写

5. 经验总结与最佳实践

5.1 问题处理流程
  1. 优先恢复服务:通过调整Merge策略快速止血
  2. 保留现场:在重启前收集堆栈、日志和指标
  3. 根因分析:通过代码走查和日志验证假设
  4. 彻底修复:升级或应用补丁
5.2 ClickHouse运维建议
  1. Schema变更时:

    • 避免在生产高峰执行
    • 提前评估影响范围
    • 考虑使用ALTER TABLE ... MODIFY SETTING调整Merge策略
  2. 监控关键指标:

    SELECT * FROM system.mutations WHERE is_done = 0; SELECT * FROM system.replication_queue WHERE last_exception != '';
  3. 复杂类型使用:

    • 谨慎使用多层嵌套的LowCardinality
    • 为复杂类型显式指定默认值
5.3 深度技术理解

通过这次事故,我们深入理解了:

  1. Merge算法选择逻辑:

    if (sum_rows > settings.vertical_merge_algorithm_min_rows && sum_bytes > settings.vertical_merge_algorithm_min_bytes) return MergeAlgorithm::Vertical;
  2. Substream处理流程:

    • 通过ISerialization::enumerateStreams()收集子列
    • 每个子列对应独立的存储文件
  3. 类型系统转换:

    • ColumnNothing作为占位符
    • 需要显式处理到目标类型的转换

这次问题的解决过程再次证明:在分布式系统中,表面简单的操作(如加列)可能引发深层次的系统交互问题。关键在于建立系统的全局视角,并掌握从现象到本质的分析方法。

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

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

立即咨询