ClickHouse v20.8.10.13-lts 补丁深度解读:copier 非分区表段错误与 MergeTree 压缩块设置初始化修复
【免费下载链接】ClickHouseClickHouse® is a real-time analytics database management system项目地址: https://gitcode.com/GitHub_Trending/cli/ClickHouse
本文聚焦 ClickHouse 官方 LTS 补丁版本 v20.8.10.13-lts(相对 v20.8.9.6-lts)所包含的两个核心 Bug 修复:clickhouse-copier在处理非分区表时触发段错误(Segfault),以及MergeTreeWriterSettings中max_compress_block_size被错误地用min_compress_block_size初始化导致压缩块异常。读者将从中理解 ClickHouse 补丁发布机制的构成、两个 Bug 的根因与影响面,并掌握min_compress_block_size/max_compress_block_size两个关键设置项的语义与底层实现约束。
补丁版本背景:LTS 分支上的 Bug 修复
v20.8是 ClickHouse 的长期支持(LTS)版本线。LTS 分支上的补丁发布通常不引入新功能,而是以"Bug Fix"形式回移植(Backport)在主线或其他分支上已经修复的缺陷,从而让生产环境可以在不升级大版本的前提下获得稳定性修复。
v20.8.10.13-lts 相对 v20.8.9.6-lts 只包含两条 Bug 修复,均带有"Backported in"标注,说明它们是通过回移植机制进入该补丁线的:
- 修复
clickhouse-copier处理非分区表时抛出的段错误(对应上游 issue #15235,修复 PR #17248,作者 Qi Chen); - 修复
MergeTreeWriterSettings使用min_compress_block_size错误地初始化max_compress_block_size的问题(对应 issue #18155,修复 PR #17833,作者 flynn)。
两条修复一个涉及运维复制工具的数据安全(崩溃),一个涉及 MergeTree 写入路径的压缩行为(性能与资源),值得分别深入剖析。
修复一:clickhouse-copier 处理非分区表时的段错误
copier 工具的定位
clickhouse-copier是 ClickHouse 在 v20.8 时代提供的跨集群表数据复制工具:它根据配置文件描述的任务,将一个 ClickHouse 集群(源)中的表数据复制到另一个集群(目标),并借助 ZooKeeper 协调多个并发 worker 分片执行。它天然以"分区"(partition)为基本调度单元来划分任务与进度。
Bug 现象与根因推断
根据补丁说明,当 copier 处理非分区表(即建表时未指定PARTITION BY,整张表只有一个系统自动生成的虚拟分区)时,会抛出段错误(Segfault)。
从 copier 的任务划分逻辑可以推断:其内部调度流程通常需要从表的分区表达式解析出分区键值,并基于分区值构造复制任务的粒度单元。对于非分区表,分区解析路径返回的是空/缺失的分区键描述,若下游代码直接对该描述进行指针或容器解引用而未做空值保护,就会触发空指针解引用(Segfault),而不是返回一个可处理的错误。
该场景的现实意义在于:ClickHouse 允许在不指定PARTITION BY的情况下建表(此时数据全部落入单一虚拟分区),这种表在小型分析场景中非常常见。若用户用 copier 复制这类表,旧版本会直接崩溃,任务中断且不易定位。修复后 copier 能够正确识别并处理"无分区键"的表结构。
当前仓库中的演化
值得注意的是,在当前仓库的 programs 目录中已不存在clickhouse-copier程序(程序清单见 programs/CMakeLists.txt),说明该工具在后续版本中已被逐步淘汰或迁移。因此本条修复的代码级验证应以上游 v20.8 分支的programs/copier源码为准;在当前主线仓库中,与该问题相关的表分区概念仍在 src/Storages/MergeTree 中完整保留。
修复二:MergeTreeWriterSettings 的压缩块大小初始化 Bug
问题描述
MergeTreeWriterSettings是 MergeTree 家族存储引擎在写入数据 part 时使用的设置结构体。该 Bug 的核心在于:构造函数中max_compress_block_size(最大压缩块大小)被错误地赋值成了min_compress_block_size(最小压缩块大小)的值,导致两者相等,压缩块行为被显著扭曲。
两个设置项的语义差异
两个设置项在 src/Core/Settings.cpp 中有着明确定义与默认值:
| 设置项 | 默认值 | 语义 |
|---|---|---|
min_compress_block_size | 65,536(64 KiB) | 为了降低查询处理延迟,当写入下一个 mark 时,若未压缩数据块大小已达到该值则触发压缩;实际块大小(在小于max_compress_block_size的前提下)不小于该值,且不小于一个 mark 的数据量 |
max_compress_block_size | 1,048,576(1 MiB) | 写入表之前未压缩数据块的最大大小;设置更小的块大小通常会略微降低压缩比,但因缓存局部性提升,压缩/解压速度会略微增加,内存消耗降低 |
文档同时给出典型示例:若建表时index_granularity为 8192,写入 UInt32 列(每值 4 字节)时每 8192 行仅 32 KB,因小于 64 KiB,会每两个 mark 形成一个压缩块;而写入平均 60 字节/值的 URL String 列时,每 8192 行接近 500 KB,超过 64 KiB,因此每个 mark 都会独立形成压缩块,读取单个 mark 范围时无需解压额外数据。
初始化 Bug 的实际影响
修复前的错误实现使max_compress_block_size变为 64 KiB(与min相同)。其直接后果是:
- 写路径中块攒到 64 KiB 即被压缩,压缩块显著小于应有的 1 MiB;
- 更小的压缩块导致压缩率下降(固定开销占比上升),磁盘占用增加;
- 同时 mark 与压缩块的对齐关系被打乱,读取路径可能解压更多块,吞吐受损。
由于该结构体贯穿 MergeTree 的写入、合并(merge)等关键路径,这一 Bug 会静默影响所有 MergeTree 表的写入与合并行为,属于典型的"不影响正确性、但明显劣化资源与性能"的缺陷,因此通过 LTS 补丁回移植修复。
当前源码中的防御性设计
在当前仓库中,MergeTreeWriterSettings定义于 src/Storages/MergeTree/MergeTreeIOSettings.h:
- 结构体持有
min_compress_block_size与max_compress_block_size两个独立字段(L121-L122); - 构造函数接收
global_settings、query_write_settings、storage_settings与data_part等参数,分别从全局设置、查询级写设置与表级 MergeTree 设置中解析取值; - 结构体声明了
MAX_COMPRESS_BLOCK_SIZE = 256 MiB上限常量,注释明确说明其作用是"防止 fuzzed 或错误配置的设置引发荒谬的内存分配"(L117-L119)。
在实现文件 src/Storages/MergeTree/MergeTreeIOSettings.cpp 中,构造逻辑采用std::min对存储级设置与全局设置取较小值,并对取值范围进行收敛,确保最终写入 part 的压缩块大小不会越界。
使用 MergeTreeWriterSettings 的关键路径
该结构体被 MergeTree 写入与合并链路广泛消费,主要包括:
- src/Storages/MergeTree/IMergeTreeDataPartWriter.h:part 写入器基类,构造函数接收
MergeTreeWriterSettings并通过getWriterSettings()暴露(L43、L77); - src/Storages/MergeTree/MergeTask.cpp:后台合并任务;
- src/Storages/MergeTree/MergedBlockOutputStream.cpp 与 src/Storages/MergeTree/MergedColumnOnlyOutputStream.cpp:合并/列式增量输出流;
- 各 part 格式实现:MergeTreeDataPartCompact.cpp、MergeTreeDataPartWide.cpp 及对应 Writer 类。
也就是说,MergeTreeWriterSettings是连接"用户可配置的压缩块大小"与"磁盘上实际 part 布局"之间的核心枢纽,其初始化正确性直接影响所有 MergeTree 表的写入效率与空间占用。
如何验证与升级到该补丁
对于运行在 v20.8 LTS 线上的用户:
- 确认当前版本:执行
clickhouse-server --version,若低于 20.8.10.13,则上述两个 Bug 均可能存在; - 验证 copier 场景:若使用
clickhouse-copier复制未设置PARTITION BY的表,旧版本会出现段错误,升级后应可正常完成复制; - 观察压缩块行为:升级后可通过
system.parts观察 part 的压缩块统计(bytes_on_disk与未压缩bytes的比值),写入/合并路径的压缩块大小将恢复为按max_compress_block_size(默认 1 MiB)约束的正常水平; - 使用注意:
min_compress_block_size与max_compress_block_size均为官方标注的"专家级设置",文档明确建议入门用户不要修改(见 src/Core/Settings.cpp 中的注释),除非明确理解其性能权衡。
小结
v20.8.10.13-lts 虽只包含两条修复,但覆盖了运维复制工具的数据路径崩溃与 MergeTree 写入路径的压缩行为偏差两类典型问题,体现了 ClickHouse LTS 补丁"只修不增、聚焦稳定性"的发布哲学。对正在维护 MergeTree 集群写入性能或依赖 copier 做数据迁移的用户,本补丁值得优先升级;对开发者而言,MergeTreeIOSettings的防御性约束(256 MiB 上限 +std::min收敛)也为如何防止设置参数污染底层写入路径提供了良好的工程参考。
【免费下载链接】ClickHouseClickHouse® is a real-time analytics database management system项目地址: https://gitcode.com/GitHub_Trending/cli/ClickHouse
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考