ClickHouse v21.2.7.11-stable 补丁版本解析:七项 Bug 修复背后的分布式一致性、加密与性能细节
【免费下载链接】ClickHouseClickHouse® is a real-time analytics database management system项目地址: https://gitcode.com/GitHub_Trending/cli/ClickHouse
v21.2.7.11-stable 是 ClickHouse 21.2 分支的一个补丁(patch)版本,相比 v21.2.6.1-stable 主要聚焦于稳定性修复,不引入新功能。本文以该版本的官方 Changelog(见 docs/changelogs/archive/v21.2.7.11-stable.md)为骨架,逐条剖析每项修复的问题成因、影响范围与源码佐证,帮助运行 21.2 分支的读者判断升级价值,并为研究 ClickHouse 分布式查询、副本合并、加密函数与外部字典等机制的读者提供一条按图索骥的路径。
版本概况:一次纯粹的稳定性补丁
该版本 Changelog 的标题是ClickHouse release v21.2.7.11-stable FIXME as compared to v21.2.6.1-stable。其中FIXME是发布流程中用于占位待替换的标记,说明该条目由自动化工具生成,最终发布时会被正式描述替换;整体内容结构分为两大部分:
- Bug Fix:7 个从主线回溯(backport)的缺陷修复,每个条目都标注了对应的上游 PR 与 issue 编号,方便追溯完整讨论;
- NOT FOR CHANGELOG / INSIGNIFICANT:1 个不面向用户公告的内部修复。
值得注意的是,这类补丁版本遵循 ClickHouse 的发布惯例:21.2 属于旧版本分支,所有修复都是将新版本中已合入的修复回溯移植而来,因此本版本不包含任何新功能或破坏性变更,升级风险极低,主要收益是稳定性。
修复一:自定义 ZooKeeper 集群下 Replicated*MergeTree 的元数据泄漏
问题描述:当使用非默认(自定义)ZooKeeper 集群的 Replicated*MergeTree 表被 DROP 时,存在元数据泄漏问题。修复见上游 PR #21119,回溯 issue 为 #21206。
原理分析:Replicated 系列表引擎(ReplicatedMergeTree、ReplicatedSummingMergeTree 等)依赖 ZooKeeper 保存复制元数据(如分区副本路径、日志指针等)。ClickHouse 支持通过<zookeeper>配置段为不同表指定自定义 ZooKeeper 集群,此时删除表时需要向正确的ZooKeeper 集群发送清理请求。如果清理逻辑未按表实际关联的集群定位元数据路径,就会残留孤儿元数据节点——这些节点会持续占用 ZooKeeper 存储,长期运行后可能拖慢集群并增加运维排查成本。该修复确保 DROP 操作后元数据被完整清理,避免泄漏。
升级建议:使用了多 ZooKeeper 集群配置(<remote_servers>与自定义<zookeeper>段)的部署建议升级,并可在升级后使用zookeeper表函数或system.zookeeper系统表检查是否存在残留的副本元数据路径。
修复二:Kafka 场景下的 Avro 格式解析
问题描述:修复了 Kafka 表引擎消费 Avro 格式数据时的解析问题,对应 issue #21437 与 PR #21438。
原理分析:ClickHouse 通过 Kafka 引擎表(ENGINE = Kafka())接入消息队列,消息体格式由format参数决定。Avro 是一种带 schema 的二进制序列化格式,其解析链路位于 src/Processors/Formats/Impl/AvroRowInputFormat.cpp 及其配套的 AvroConfluentSchemaRegistry.h(用于对接 Confluent Schema Registry)。从源码结构看,该问题通常与消息中 schema 的读取、二进制块的边界处理或 schema 缓存相关;由于 Kafka 消费是流式的,Avro 解析器对分片消息的处理稍有偏差就可能抛出解析异常或读到错位数据。此修复保证了 Kafka + Avro 组合下消息的正确解析。
验证方式:升级后可创建 Kafka 引擎表并指定format = 'Avro',用SELECT count()与抽样SELECT *核对消费数据完整性。
修复三:optimize_skip_unused_shards 下的 "Cannot find column" 错误
问题描述:当开启optimize_skip_unused_shards且实际命中 0 个分片时,可能抛出Cannot find column错误。修复见 PR #21579,回溯 issue 为 #21855。
原理分析:optimize_skip_unused_shards是分布式查询的核心优化开关,其定义在 src/Core/Settings.cpp:当WHERE/PREWHERE中包含分片键(sharding key)条件时,ClickHouse 可以据此跳过不包含目标数据的分片,只把查询发给少数几个分片。该设置的完整参数族还包括:
| 设置项 | 默认值 | 说明 |
|---|---|---|
optimize_skip_unused_shards | 0(关闭) | 是否启用按分片键跳过无用分片;开启前必须确认数据确实按分片键分布,否则结果不正确 |
optimize_skip_unused_shards_limit | 1000 | 分片键值数量的上限,超过则关闭该优化,避免IN (...)列表过大时收益有限 |
optimize_skip_unused_shards_rewrite_in | 1 | 对远端分片重写IN,剔除不属于该分片的值 |
force_optimize_skip_unused_shards | 0 | 无法跳过分片时是否强制抛异常(1仅在表有分片键时禁用查询,2无条件禁用) |
optimize_skip_unused_shards_nesting | 0 | 控制优化在嵌套 Distributed 表的层级作用范围 |
以上参数定义均可直接在 src/Core/Settings.cpp 中查到,相关实现在 src/Interpreters/ClusterProxy/executeQuery.cpp 与 src/Storages/StorageDistributed.cpp 中。本次修复针对的正是分片裁剪结果为空(0 个分片)时的边界情况:此时查询计划中可能缺少列引用,导致执行阶段报Cannot find column。修复后,空分片场景会被正确处理,查询直接返回空结果而非报错。
修复四:水平合并(horizontal merge)的 fsync_part_directory
问题描述:修复了水平合并过程中fsync_part_directory缺失/未正确执行的问题,见 PR #21642,回溯 issue 为 #21688。
原理分析:合并(merge)是 MergeTree 家族的核心操作,分为把多个 part 直接拼接数据的水平合并与按列重写的其他合并。合并完成后,新 part 的目录与文件必须通过fsync落盘,才能在断电或进程崩溃后保证数据不丢失、不出现半成品 part。该函数在 src/Storages/MergeTree/IDataPartStorage.h 及相关实现中定义,合并流程会调用它把目录变更刷入持久化存储。此次修复补上了水平合并路径上遗漏的 fsync 调用,属于**数据持久性(durability)**层面的修复——它不改变合并结果的逻辑,但显著降低异常掉电后 part 损坏的风险。
升级建议:对数据可靠性要求高、追求“零丢失”语义的部署(尤其是fsync_after_insert/ merge 相关持久化设置被显式配置的集群)应尽快升级。
修复五:async_socket_for_remote 下分布式请求的取消
问题描述:当async_socket_for_remote=1时,分布式请求(例如select * from remote('127.{2,3}', system.numbers) limit 100这种带limit的多分片简单查询)可能无法被正确取消。修复见 PR #21643,回溯 issue 为 #21795。
原理分析:async_socket_for_remote定义于 src/Core/Settings.cpp,默认值为1(开启),作用是“执行远程查询时异步读取 socket”,与其配套的async_query_sending_for_remote(默认1)负责异步建连与发送。二者在 src/Interpreters/ClusterProxy/executeQuery.cpp 处被传入远程查询执行器。
异步 I/O 的收益是:当部分分片响应慢时,主节点不必阻塞等待,可以并行处理已就绪的分片数据。但异步化也引入了取消语义的复杂度:像... LIMIT 100这样的查询在拿到足够行数后需要主动取消其余分片的远程执行,如果取消信号在异步读路径上丢失,慢分片的查询会继续空转占用资源。本次修复确保在异步模式下取消请求能被可靠下发到各分片,避免资源泄漏与不必要的负载。
修复六:Distinct 组合子 + 两阶段聚合的崩溃
问题描述:修复了聚合函数带Distinct组合子(如uniqDistinct、sumDistinct等,即agg -Distinct系列)在两阶段聚合(two-level aggregation)模式下可能崩溃的问题。修复见 PR #21818,回溯 issue 为 #21879。
原理分析:两阶段聚合是 ClickHouse 处理大基数分组时的关键机制:数据量大时先按分桶做局部聚合(第一级),再合并各桶结果(第二级)。Distinct组合子要求聚合器在执行前先对输入去重,其状态合并逻辑与两阶段架构的桶间数据交换需要严格匹配。该修复是对上游 PR #18365 的 follow-up。Changelog 特别注明该问题“仅在生产环境可复现,暂无测试用例”——这提示它依赖特定的数据分布与并发调度时序,普通单元测试难以触发。
影响面:凡是使用*Distinct系列聚合函数、且数据量足以触发两阶段聚合(通常由max_threads与分组键基数共同决定)的查询都可能受到影响,建议升级后重点回归这类聚合查询。
修复七:hashed 外部字典加载的内存占用回退
问题描述:回退(revert)了上游 PR #15454,因为它在加载hashed 类型外部字典时可能导致内存占用显著上升。见 PR #21948,关闭 issue #21935。
原理分析:ClickHouse 的外部字典支持hashed布局(把整张字典一次性加载进哈希表),常用于维表关联查询。PR #15454 的原始意图通常是优化字典访问性能,但其副作用是加载路径上分配了额外的中间结构,使大字典的内存占用成倍增加。在“时间换空间”的权衡下,维护者选择回退该改动、恢复旧的内存占用水平,以保障大字典场景的内存安全。此类回退在 Changelog 中并不罕见,属于性能优化与资源消耗之间的再平衡,体现了 ClickHouse 对内存占用敏感场景的保守策略。
升级建议:使用大体积hashed布局外部字典(如数 GB 级维表)且内存预算紧张的部署,建议升级本版本以获得更低的内存峰值。
修复八:decrypt 函数 AEAD 模式的最小密文长度校验
问题描述:decrypt函数在AEAD 模式(aes-128-gcm、aes-256-gcm等)下缺少对密文最小长度的检查,可能触发越界读取或异常行为。修复见 PR #22064,关闭 issue #21897。
原理分析:加解密函数族实现在 src/Functions/FunctionsAES.h 中。AEAD(Authenticated Encryption with Associated Data)模式(如 RFC 5116 定义的RFC5116_AEAD_AES_GCM,见 FunctionsAES.h)会在密文末尾附加认证标签(tag),因此合法的密文长度必须大于等于 tag 长度。修复后的解密路径会先做长度校验,若密文小于 tag 大小则抛出明确的异常:
// src/Functions/FunctionsAES.h#L1010-L1018 if constexpr (mode == CipherMode::RFC5116_AEAD_AES_GCM) { if (string_size > 0) { if (string_size < tag_size) throw Exception(ErrorCodes::BAD_ARGUMENTS, "Encrypted data is smaller than the size of additional data for AEAD mode, cannot decrypt."); resulting_size -= tag_size; } }安全意义:该修复属于典型的输入校验加固。此前传入过短密文可能让解密逻辑按错误的偏移解析数据,轻则返回垃圾结果,重则触发未定义行为;修复后行为变为可控的、带错误码的异常,符合“快速失败”的安全实践。使用encrypt/decrypt、aes_encrypt_mysql/aes_decrypt_mysql系列函数做应用层加密的读者值得关注。
附:非公开的内部修复——LRUCache 的异常安全插入
Changelog 末尾的NOT FOR CHANGELOG / INSIGNIFICANT分类包含一条内部修复(PR #21891):LRUCache 修复了非异常安全的元素插入。它不面向用户公告,原因是该问题不产生用户可见的错误行为,而是代码健壮性层面的改进——例如在插入新缓存项时如果发生异常,可能把缓存内部链表或哈希结构置于不一致状态,进而影响后续访问。这类修复体现了 ClickHouse 对基础数据结构(缓存、哈希表等)异常安全性的持续打磨,虽然不直接可见,却为上层功能的稳定性兜底。
升级与验证指引
综合来看,v21.2.7.11-stable 是一次风险极低的稳定性补丁发布,对以下场景尤其有价值:
- 多 ZooKeeper 集群 + Replicated 表:修复元数据泄漏;
- Kafka + Avro 消费链路:修复格式解析;
- 开启
optimize_skip_unused_shards的分布式集群:消除空分片边界下的报错; - 依赖合并持久性的高可靠部署:补全水平合并的 fsync;
- 使用
*Distinct聚合、hashed 外部字典、AEAD 加解密的查询路径。
升级后建议按以下顺序做回归验证:
- 对每张 Replicated 表执行
SELECT count()并 DROP/CREATE 往返一次,检查 ZooKeeper 中是否残留路径; - 重放 Kafka 消费任务,用
SELECT * FROM kafka_table LIMIT n核对 Avro 消息字段完整性; - 执行含分片键
WHERE条件(命中 0 分片的极端场景)的分布式查询,确认返回空结果而非Cannot find column; - 运行含
uniqDistinct/sumDistinct的大基数分组查询与 AEAD 模式decrypt用例(含非法短密文输入)。
延伸阅读
- 完整 Changelog 原文:docs/changelogs/archive/v21.2.7.11-stable.md
- 分布式跳分片优化相关设置定义:src/Core/Settings.cpp
- 异步远端查询设置定义:src/Core/Settings.cpp
- AEAD 加解密实现与长度校验:src/Functions/FunctionsAES.h
- 远程查询执行与取消逻辑:src/Interpreters/ClusterProxy/executeQuery.cpp
- Avro 输入格式实现:src/Processors/Formats/Impl/AvroRowInputFormat.cpp
【免费下载链接】ClickHouseClickHouse® is a real-time analytics database management system项目地址: https://gitcode.com/GitHub_Trending/cli/ClickHouse
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考