news 2026/9/11 4:02:26

ClickHouse v21.11.8.4-stable 版本修复深度解读:从复制队列挂起到稀疏哈希字典性能回归

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
ClickHouse v21.11.8.4-stable 版本修复深度解读:从复制队列挂起到稀疏哈希字典性能回归

ClickHouse v21.11.8.4-stable 版本修复深度解读:从复制队列挂起到稀疏哈希字典性能回归

【免费下载链接】ClickHouseClickHouse® is a real-time analytics database management system项目地址: https://gitcode.com/GitHub_Trending/cli/ClickHouse

本篇文章基于 ClickHouse 仓库中的 v21.11.8.4-stable 变更日志,深入解析该稳定版补丁相对于 v21.11.7.9-stable 修复的 7 个用户可见缺陷与 2 个内部改进。每项修复都对应一个真实的线上故障场景——复制队列卡死、DROP TABLE 并发崩溃、物化视图 LOGICAL_ERROR、字典查询性能骤降等。读者读完后,不仅能明确知晓这些 Bug 的表现与触发条件,还能借助仓库源码理解其底层成因与修复思路,为排查同类问题提供直接参考。

版本概览:一次聚焦线上稳定性与并发正确性的补丁发布

v21.11.8.4-stable 是 ClickHouse 21.11 稳定分支的第四次补丁发布,变更点全部集中在Bug Fix(用户可见的官方稳定版误行为)NOT FOR CHANGELOG / INSIGNIFICANT(内部优化)两类,不包含任何新功能或破坏性变更。这意味着本次升级的核心价值在于消除已知的稳定性风险,建议受影响的用户优先评估升级。

修复清单速览:

修复项影响模块故障现象
projection 意外移除MergeTree / 物化投影detach 数据 part 时投影被错误删除
复制队列条目挂起ReplicatedMergeTree临时目录残留导致复制队列阻塞最长 1 天
sparse_hashed 字典性能骤降字典(Dictionary)顺序键场景哈希函数错误,查询性能严重劣化
表生命周期竞态存储层并行 DROP TABLE 与 INSERT 导致 use-after-free
RabbitMQ 启动异常RabbitMQ 表引擎启动时延迟创建 channel 后异常消失
物化视图 LOGICAL_ERROR物化视图目标表为 JOIN/SET 表时报 LOGICAL_ERROR
fuzzBits 崩溃随机函数多个相同 FixedString 输入触发崩溃

修复一:Detach Parts 时投影(Projection)被意外移除

现象:在部分场景下,当用户 detach(分离)MergeTree 数据 part 时,原本附加在该 part 上的投影(projection)会被意外删除,造成数据语义丢失。该修复由 Amos Bird 提交,对应 PR #32067(由 issue #32679 回溯合入)。

背景知识:ClickHouse 的投影功能允许在 part 内部维护轻量级物化聚合结果,是加速GROUP BY等聚合查询的重要手段。投影的生命周期与 part 紧密绑定,一旦 part 在被 detach 的过程中错误清理了投影元数据,重新 attach 后投影将无法被查询优化器使用。

从源码看 detach 的清理路径:MergeTree 表的 detach 操作最终会进入 src/Storages/MergeTree/IMergeTreeDataPart.cpp 所定义的 part 生命周期管理逻辑。该修复的关键在于收紧「哪些资源在 detach 时应被清除」的判断条件,确保投影等重建成本高的附属对象被保留,而不是与临时清理对象一并移除。

实操建议:升级到 v21.11.8.4 及以上后,若此前曾因该 Bug 丢失投影,可通过重新执行ALTER TABLE ... MATERIALIZE PROJECTION重建受影响的投影;日常运维中建议在变更 part 状态(detach/drop)前先通过system.partssystem.projection_parts确认投影状态。

修复二:复制队列条目挂起与 temporary_directories_lifetime 的治理

现象:部分复制队列条目可能长时间挂起,持续报错形如Directory tmp_merge_<part_name>Part ... (state Deleting) already exists, but it will be deleted soon,挂起时长可达temporary_directories_lifetime(默认1 天),对应 issue #29616,由 Alexander Tokmakov 修复(PR #32201,由 issue #32543 回溯合入)。

原因分析:MergeTree 在合并、突变等后台操作中会创建tmp_merge_<part_name>之类的临时目录。当进程异常中断或操作被取消时,这些临时目录可能残留;后台清理线程默认要等到temporary_directories_lifetime到期后才会回收它们,而复制队列中的后续操作恰好依赖同名 part 目录就绪,于是出现「队列条目干等临时目录过期」的连锁阻塞。

源码佐证——清理线程的真实逻辑:在 src/Storages/MergeTree/ReplicatedMergeTreeCleanupThread.cpp 中可以看到,复制表的后台清理线程iterate()每轮会执行clearOldPartsAndRemoveFromZK(),并在共享锁保护下调用storage.clearOldTemporaryDirectories(...),超时参数正是取自 MergeTree 设置项temporary_directories_lifetime(该设置定义于 src/Storages/MergeTree/MergeTreeSettings.cpp)。修复使清理线程能够及时识别并处理与复制队列冲突的残留临时目录,从而打破挂起。

实操建议

  • 临时目录的保留时长可通过 MergeTree 设置调整:<merge_tree><temporary_directories_lifetime>3600</temporary_directories_lifetime></merge_tree>(单位秒)。
  • 若线上已出现Part ... already exists, but it will be deleted soon类报错,可结合system.replication_queue观察队列条目状态,并检查system.parts中是否存在残留的tmp_前缀目录。
  • 建议定期巡检数据目录,避免异常中断(OOM、kill -9)后遗留大量临时目录挤占磁盘。

修复三:sparse_hashed 字典在顺序键下的性能回归(错误哈希函数)

现象sparse_hashed布局的字典在处理顺序排列的键(如自增 ID)时性能明显劣化。根因是采用了不合适的哈希函数,由 Azat Khuzhin 修复(PR #32536,由 issue #32769 回溯合入)。

源码级解读——为什么是哈希函数的问题:ClickHouse 的HashedDictionary模板同时支持Hashed(紧凑 HashMap)与SparseHashed(内存优先)两种布局,见 src/Dictionaries/HashedDictionary.h:

  • Hashed使用内置HashMap<UInt64, Value, DefaultHash<UInt64>, ...>
  • SparseHashed根据键值对的大小在google::sparse_hash_mapPackedHashMap之间选择(判断函数useSparseHashForHashedDictionary()位于 src/Dictionaries/HashedDictionaryCollectionType.h)。

在 src/Dictionaries/HashedDictionaryCollectionType.h 的源码注释中明确写道:稀疏哈希表统一改用DefaultHash<>而非std::hash<>,原因是DefaultHash<>在处理「顺序键 + 随机访问」场景(如SELECT number FROM numbers(3000000) ORDER BY rand()式负载)时显著优于std::hash<>,差距可达10 倍以上。本次修复正是纠正了 sparse 布局中哈希函数的选择,使顺序键场景不再退化为病态分布。

延伸:PackedHashMap 为何存在:同文件注释还给出了各类哈希结构在 10 亿元素下的内存占用估算——google::sparse_hash_map约 26 GiB、内置HashMap约 35 GiB、PackedHashMap约 22 GiB、sparse_hash_map<packed_pair>约 17 GiB,同时说明google::sparse_hash_map因无法使用紧凑结构、且存在大量 realloc 导致 jemalloc 下 33% 碎片化等问题。因此当<K,V>对较小(sizeof(V) <= 8且键为 POD)时,PackedHashMap反而是更优选择,这也解释了useSparseHashForHashedDictionary()的判定逻辑。

实操建议:使用SPARSE_HASHED布局(或根据键值对大小自动退化为稀疏结构)的字典用户,升级后应复测以顺序键为主的高频查询;字典性能可通过system.dictionariesbytes_allocatedquery_counthit_rate等指标观察。

修复四:并行 DROP TABLE 与 INSERT 引发的表生命周期竞态(use-after-free)

现象:在并行执行DROP TABLEINSERT时,存在表对象生命周期管理缺陷,可能造成 use-after-free(悬空指针访问)。该问题对稳定版用户是潜在崩溃风险,由 Azat Khuzhin 修复(PR #32572,由 issue #32635 回溯合入)。

背景知识:ClickHouse 的表存储对象(StoragePtr)被查询、后台任务、DDL 等多个执行上下文共享持有。DROP TABLE会触发对象析构流程,而正在执行的INSERT若仍持有指向该对象的裸引用,就可能出现释放后再访问的竞态。此类问题在并发压力下表现为偶发段错误或std::bad_alloc类异常,排查难度高。

从源码结构看修复方向:该修复属于存储层对象生命周期管理范畴,核心思路是保证 DROP 流程与正在进行的写入流程对表对象的引用计数/锁持有关系保持一致,避免在对象析构后仍有执行路径访问其成员。由于本补丁不改变 SQL 语义,升级后无需调整使用方式,但建议 DROP 类操作与批量写入错峰执行,以降低并发竞态暴露面。

修复五:RabbitMQ 表引擎启动异常——延迟 channel 创建

现象:RabbitMQ 存储(表引擎)在启动阶段可能抛出异常,原因是在连接尚未就绪时过早创建了 channel。修复方式是延迟 channel 的创建时机,由 Kseniia Sumarokova 修复(PR #32584,由 issue #32633 回溯合入)。

背景知识:ClickHouse 的 RabbitMQ 表引擎用于将消息队列作为表读写。启动时会建立 AMQP 连接、创建 channel、声明队列并开始消费。若启动流程中 channel 创建早于底层连接完成握手,在断线重连等场景下会触发协议层异常。

实操建议:使用ENGINE = RabbitMQ的用户升级后需重启实例以加载新代码;如启动阶段仍观察到连接异常,可检查 RabbitMQ 服务端日志与system.tables中该表的元数据状态,并确认网络与鉴权配置(用户名、密码、vhost)无误。

修复六:物化视图目标为 JOIN / SET 表时的 LOGICAL_ERROR

现象:当物化视图(Materialized View)的目标表是JOINSET引擎表时,写入过程会抛出LOGICAL_ERROR(逻辑错误异常)。由 Raúl Marín 修复(PR #32669,由 issue #32891 回溯合入)。

背景知识:物化视图在插入数据时会同步向目标表写入;JOINSET引擎表属于内存型、非持久化的特殊表,其写入路径与普通 MergeTree 目标表存在差异。旧版本未对这两类目标做兼容处理,导致内部断言失败并被提升为LOGICAL_ERROR

实操建议

  • 生产环境不建议将 JOIN/SET 引擎表作为物化视图目标(数据不落盘、重启即失),此类表更适合作为查询侧加速结构;
  • 若已存在该组合,升级后写入不再报错,但请确认数据仅存在于内存中这一限制;
  • 排查历史 LOGICAL_ERROR 时,可结合system.query_log与物化视图定义(system.tablescreate_table_query)定位涉及的目标表类型。

修复七:fuzzBits 在多个相同 FixedString 输入下崩溃

现象:当fuzzBits函数配合多个相同的FixedString输入使用时可能崩溃(关闭 issue #32737),由 SuperDJY 修复(PR #32755,由 issue #32792 回溯合入)。

源码级解读——fuzzBits 的实现与约束fuzzBits定义于 src/Functions/fuzzBits.cpp,功能是对输入字符串的每一位以概率p进行翻转(XOR 掩码扰动):

  • 签名与类型约束fuzzBits(s, p),第一个参数必须是StringFixedString,第二个参数必须是常量浮点数,取值范围限定为0.01.0(越界抛ARGUMENT_OUT_OF_BOUND),见 src/Functions/fuzzBits.cpp;
  • 随机性设计:内部使用pcg64_fast伪随机数生成器,通过getXorMask()将随机数按位换算为翻转掩码(ptr_out[i] = ptr_in[i] ^ mask)。函数刻意声明为非确定性(isDeterministic()isDeterministicInScopeOfQuery()均为 false),并强制第二个参数为常量,避免对常量列展开导致「一行结果复制到所有行」的错误语义,见 src/Functions/fuzzBits.cpp;
  • 修复点:此前的实现未充分处理多个相同 FixedString 输入时列数据的边界条件,导致内存访问越界。修复后 FixedString 路径通过common::mulOverflow精确计算input_rows_count * n的缓冲区大小(src/Functions/fuzzBits.cpp),杜绝了溢出与越界写入。

使用示例(摘自该函数自带的文档化示例):

SELECT fuzzBits(materialize('abacaba'), 0.1) FROM numbers(3)

每次执行结果不同,例如:

┌─fuzzBits(materialize('abacaba'), 0.1)─┐ │ abaaaja │ │ a*cjab+ │ │ aeca2A │ └───────────────────────────────────────┘

实操建议fuzzBits常用于数据脱敏、模糊测试与数据增强场景。使用FixedString输入时,概率参数建议按字节扰动需求设置在0.001~0.5之间,避免过高概率使数据完全失真。

内部改进:ProtobufSchemas 数据竞争与常量条件优化

除用户可见修复外,本版本还合入了两项内部改动(归入 NOT FOR CHANGELOG / INSIGNIFICANT):

  • 修复 ProtobufSchemas 数据竞争(PR #27822,filimonov 提交):ProtobufSchemas全局模式缓存的并发访问存在 data race,在多线程解析 Protobuf 格式(如Protobuf/ProtobufSingle输入输出格式)时可能产生未定义行为。该项为线程安全加固,不改变外部行为。
  • 始终启用常量条件优化(PR #32858,Nikolai Kochetov 提交):将const-condition-if优化由条件性应用改为始终应用。该优化会在查询分析阶段对if/multiIf中值为常量的分支进行折叠,减少执行期不必要的表达式求值,属于查询计划层面的稳定收益,对大量使用常量条件过滤的报表类 SQL 有正向影响。

升级建议与验证方式

  1. 升级路径:v21.11.x 用户可平滑升级至 v21.11.8.4-stable(补丁版本不引入破坏性变更);建议先在灰度环境执行clickhouse-server滚动升级并观察系统日志与system.metric_log
  2. 回归重点:结合本版本修复清单,建议重点回归以下场景——投影查询与 part detach/attach、复制表队列健康度(system.replication_queue)、稀疏哈希字典查询延迟、fuzzBits随机函数、物化视图写入。
  3. 故障定位参考:本版本涉及的复制清理、字典实现、函数实现等模块源码分别位于 src/Storages/MergeTree/ReplicatedMergeTreeCleanupThread.cpp、src/Dictionaries/HashedDictionaryCollectionType.h、src/Functions/fuzzBits.cpp,出现同类问题时可直接对照排查。

说明:本文所有修复条目与 PR/issue 编号均摘自 v21.11.8.4-stable 变更日志,源码佐证来自当前仓库对应文件,升级决策请以官方稳定版发布渠道为准。

【免费下载链接】ClickHouseClickHouse® is a real-time analytics database management system项目地址: https://gitcode.com/GitHub_Trending/cli/ClickHouse

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/11 4:00:59

10 秒把自己变成AI数字人:Duix.Avatar本地部署新手完整指南

10 秒把自己变成AI数字人&#xff1a;Duix.Avatar本地部署新手完整指南 【免费下载链接】Duix-Avatar &#x1f680; Truly open-source AI avatar(digital human) toolkit for offline video generation and digital human cloning. 项目地址: https://gitcode.com/GitHub_T…

作者头像 李华
网站建设 2026/9/11 4:00:31

军工级Web大文件上传方案:跨浏览器分片与加密传输实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/11 4:00:20

Spark大数据分析在餐饮行业的实战应用

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/11 3:58:23

Langfuse+WebSocket构建AI对话实时监控仪表盘

纯粹聊技术&#xff0c;聊我从0到1搭这个系统时踩过的泥坑。先把话说在前面&#xff1a;这不是一篇教程&#xff0c;更像是我做完整个项目后的复盘笔记&#xff0c;顺手把能“抄作业”的代码和配置都贴出来。你如果正准备给基于大模型的应用加一个实时监控仪表盘&#xff0c;或…

作者头像 李华
网站建设 2026/9/11 3:57:53

力扣Hot100刷题效率低?亲手搭建Java本地调试模板全攻略

先把话说在前面&#xff1a;如果你刷力扣还在“在线编辑器里手写代码、提交、看报错、再改”的死循环里打转&#xff0c;那我强烈建议你停下来&#xff0c;花一个晚上搭一套自己的力扣Hot100 Java本地模板。这套东西做完之后&#xff0c;刷题的速度、对代码的掌控感&#xff0c…

作者头像 李华
网站建设 2026/9/11 3:56:59

嵌入式AI工作台:用API构建本地化、可审计的AI辅助开发系统

1. 项目概述&#xff1a;为什么嵌入式工程师需要自己的 API 工作台&#xff1f; “嵌入式工程师的 AI 辅助开发实践&#xff1a;低成本搭一套顺手的 API 工作台”——这个标题里藏着三个被行业长期忽视却正在剧烈变化的现实&#xff1a;第一&#xff0c;嵌入式开发早已不是单打…

作者头像 李华