ClickHouse v26.2.15.4-stable 补丁版本深度解读:三大稳定性修复的源码级分析
【免费下载链接】ClickHouseClickHouse® is a real-time analytics database management system项目地址: https://gitcode.com/GitHub_Trending/cli/ClickHouse
导读
本文基于当前仓库的官方变更记录(docs/changelogs/v26.2.15.4-stable.md),逐条剖析 ClickHouse v26.2.15.4-stable 相对 v26.2.14.5-stable 引入的 3 个用户可见 Bug 修复:ASTColumnsExceptTransformer的LOGICAL_ERROR、字符串检索器StringSearcher.h的未初始化值访问,以及 CPU lease 调度器中定时器越过工作线程生命周期导致的 use-after-free。读完本文,你将理解这三处缺陷的触发场景、底层修复思路,以及如何在自建环境中复现验证,为评估是否升级补丁版本提供依据。
一、补丁版本概览:v26.2.15.4-stable 修复了什么
本次发布是一个典型的 stable 分支补丁版本,构建提交为8df18e81311,对比基线是上一个稳定版本 v26.2.14.5-stable(提交edcd1986fd8)。变更记录只包含一个分类:
- Bug Fix(用户可见的官方稳定版异常行为),共 3 项。
补丁版本的这类发布原则是:不引入新功能,只收敛稳定分支上已被社区确认并 backport 的问题。三个修复均先有上游 PR/Issue 编号(如 #99119、#100225、#101761),再以 backport 形式合入本分支(对应 #102657、#101420、#102679),这符合 ClickHouse 稳定分支的发布流程。
三个修复覆盖了三类典型问题域:查询改写(列变换器)、字符串检索(文本函数核心路径)与资源调度(CPU lease 生命周期)。下面逐一深入。
二、修复一:ASTColumnsExceptTransformer::transform中的 LOGICAL_ERROR
2.1 缺陷背景
ASTColumnsExceptTransformer是 ClickHouse 列变换器(columns transformer)机制的一部分,对应 SQL 语法中的SELECT * EXCEPT (...)与SELECT * EXCEPT 'regexp'。该机制在 src/Parsers/ASTColumnsTransformers.h 定义,实际执行逻辑位于 src/Interpreters/applyColumnsTransformer.cpp。
applyColumnsExceptTransformer(见 applyColumnsTransformer.cpp)的核心流程是:
- 若未指定正则模式,则把
EXCEPT列表中的子节点(标识符或其别名/列名)收集进expected_columns; - 遍历当前节点列表,将名称命中的列移除;
- 若声明了
EXCEPT STRICT(transformer.is_strict)且仍有未匹配到的列,则抛出NO_SUCH_COLUMN_IN_TABLE异常,提示"Columns transformer EXCEPT expects following column(s) :..."; - 若指定了正则模式,则通过
getColumnsExceptMatcher编译re2::RE2正则,用RE2::PartialMatch做部分匹配过滤列。
当查询被旧分析器与新分析器(Analyzer)以不同顺序、不同时机重复应用列变换,或EXCEPT列表中的节点形态(标识符 vs 带别名的表达式)与预期不符时,内部不变量会被破坏,从而在transform中触发LOGICAL_ERROR。这正是本补丁修复的用户可见异常:合法查询在特定组合下直接报错。
2.2 修复的意义与验证方式
修复保证了列变换器在处理EXCEPT时对节点形态(ASTIdentifier与带别名表达式)的识别路径一致,避免内部状态不一致。仓库中已有专门的状态测试脚本 tests/queries/0_stateless/01470_columns_transformers2.sql 覆盖了列变换器的组合用法,例如:
SELECT * EXCEPT 'bytes', COLUMNS('bytes') APPLY formatReadableSize FROM columns_transformers;这行语句同时使用了EXCEPT '正则'与COLUMNS(...) APPLY两种变换器,正是列变换器组合场景的典型回归用例。你在自己的实例上可以通过构造「先EXCEPT排除、再对剩余列做APPLY/REPLACE」的嵌套查询来验证该路径不再抛出LOGICAL_ERROR。
三、修复二:StringSearcher.h中的未初始化值访问
3.1 缺陷背景
StringSearcher.h(src/Common/StringSearcher.h)是 ClickHouse 字符串检索基础设施的一部分,被position、like、startsWith等高频文本函数使用。文件头部注释明确说明:这类检索器在多数场景下性能低于 Volnitsky 算法(见 src/Common/Volnitsky.h),但适用于短模式与特定调用路径。
文件内部实现了几种检索器:
CaseSensitiveStringSearcher(StringSearcher.h):区分大小写,短 needle(<32 字节)走逐字节比较循环,长 needle 委托给 StringZilla 的sz_find/sz_equal;ASCIICaseInsensitiveStringSearcher(StringSearcher.h):不区分大小写的 ASCII 折叠版本,在__AVX2__或__aarch64__ + ARM_NEON下用向量指令构建首字符缓存patl/patu与 needle 前 N 字符的cachel/cacheu,配合cachemask做无分支过滤。
修复针对的 "use-of-uninitialized-value" 属于未定义行为:在某些输入(例如特定长度的 needle、空 needle、或走非向量化回退分支)下,检索器会读取未被完整初始化的缓存或栈数据,导致 MemorySanitizer(MSan)在 CI 中报use-of-uninitialized-value。这类问题在正常构建下往往"碰巧正常",但在 sanitizer 构建、不同编译器或不同架构(x86_64 AVX2 与 aarch64 NEON)下可能产生非确定性结果。
3.2 修复的影响面
修复针对向量化缓存构造路径补齐了初始化,保证无论 needle 长度如何,cachemask与缓存字节都以确定的方式生成。值得注意的实现细节:
- 缓存掩码
full_cache_mask专门按 32 位安全方式计算((N >= 32) ? 0xFFFFFFFFu : ((1u << N) - 1u)),避免1u << 32的未定义行为; - 向量路径填充 needle 字节时使用
uint8_t needle_bytes[N] = {};零初始化后再memcpy,确保剩余字节确定。
这意味着修复不仅消除了 sanitizer 告警,也提升了检索器在不同 SIMD 架构上的确定性。如果你的部署通过positionCaseInsensitive等函数处理大量字符串检索,升级后可关注相关路径的稳定性。
四、修复三:CPU lease 调度器中定时器越过线程生命周期的 use-after-free
4.1 缺陷背景
这是三个修复中风险最高的一项。CPU lease 调度器位于 src/Common/Scheduler 目录,是 ClickHouse 并发控制/工作负载资源管理(Workload Resource Management)的一部分,相关组件包括CPULeaseAllocation、CPUSlotsAllocation、WorkloadResourceManager等。
缺陷描述为:当等待定时器(wait timer)存活时间超过其引用的ProfileEvents::Counters所属工作线程时,定时器刷新计数时访问已销毁的计数器对象,造成 use-after-free 崩溃。
根因分析:CPULeaseAllocation在调度(schedule())时需要记录等待耗时。旧实现可能在调度线程上下文里取CurrentThread::getProfileEvents()返回的计数器引用并交给ProfileEvents::Timer持有。一旦该工作线程先行退出、其Counters被销毁,而定时器仍在排队等待刷新,就会出现悬垂引用。
4.2 修复方案:用 ThreadGroup 所有权托管计数器生命周期
修复的源码证据清晰体现在 src/Common/Scheduler/CPULeaseAllocation.cpp:
// Capture query-level counters (ThreadGroup) that outlive all worker threads. // Cannot use CurrentThread::getProfileEvents() in schedule() — it returns the calling // thread's counters, which may be destroyed before the timer is flushed (UAF). wait_thread_group = CurrentThread::getGroup(); if (wait_thread_group) wait_counters = &wait_thread_group->performance_counters;即:不再捕获「调用线程」的计数器,而是捕获查询所属 ThreadGroup(ThreadGroupPtr,一个std::shared_ptr)的计数器。ThreadGroup 的生命周期覆盖该查询的所有工作线程,只要查询未结束,其performance_counters就必然存活,从而保证定时器在刷新时引用仍然有效。
与之配套的成员声明顺序也有讲究(见 src/Common/Scheduler/CPULeaseAllocation.h):
/// Stable counters for wait_timer. We cannot use CurrentThread::getProfileEvents() in /// schedule() because it returns the calling thread's counters, which may be destroyed /// before the timer is flushed — storing a Timer with a dangling Counters& causes UAF. /// The ThreadGroupPtr keeps the ThreadGroup (and its performance_counters) alive. /// Declared before wait_timer so the owner outlives the timer during member destruction. std::shared_ptr<ThreadGroup> wait_thread_group; ProfileEvents::Counters * wait_counters = &ProfileEvents::global_counters; std::optional<ProfileEvents::Timer> wait_timer;两个关键点:
wait_thread_group声明在wait_timer之前——C++ 成员按声明顺序析构,因此ThreadGroupPtr(计数器所有者)一定比定时器晚销毁,即使析构路径上也安全;- 兜底策略:当线程组不可用时(
wait_thread_group为空),wait_counters回退到进程级ProfileEvents::global_counters,后者与全局进程同生命周期,永不悬垂。
4.3 测试与复现背景
CPU lease 调度器是本版本中高度活跃且经过大规模重构的模块,仓库在 src/Common/Scheduler/Nodes/tests/gtest_workload_resource_manager.cpp 中保存了大量调度器单测,其中不乏针对生命周期问题的回归测试(该文件注释中多次出现 use-after-free、outlive等主题)。这类缺陷在并发压力大、线程频繁创建销毁的高负载场景下最容易浮出水面,官方在 CHANGELOG 中以 "user-visible misbehavior" 定性,说明其可能造成服务端进程崩溃。
五、升级建议与验证清单
5.1 是否值得升级
本次三个修复均针对 stable 分支的稳定路径:
| 修复项 | 触发场景 | 风险等级 |
|---|---|---|
ASTColumnsExceptTransformerLOGICAL_ERROR | 使用EXCEPT/APPLY/REPLACE列变换器的复杂查询 | 中等(查询报错) |
StringSearcher.h未初始化值 | 大量字符串检索函数(position/like等),sanitizer 构建更明显 | 低~中(UB 类) |
| CPU lease 调度器 use-after-free | 高并发、工作线程频繁退出的调度场景 | 高(进程崩溃) |
如果你的集群启用了工作负载资源管理(workload_path相关配置)且并发压力大,第三项值得优先关注;如果业务大量使用EXCEPT/COLUMNS列变换器组合查询,第一项会直接影响线上可用性。
5.2 升级路径
- 从 v26.2.14.5-stable 升级到 v26.2.15.4-stable 为同一 minor 版本的补丁升级,属于常规 stable 分支升级,配置兼容性风险低;
- 升级前建议先在一台节点上执行冒烟验证:跑一遍 01470_columns_transformers2.sql 中的列变换器用例,并对核心查询集做一次回归;
- 若使用 sanitizer 构建(MSan/ASan),可重点跑字符串检索与调度器相关测试(如
gtest_workload_resource_manager),验证告警清零。
5.3 关键代码路径速查
- 列变换器执行逻辑:src/Interpreters/applyColumnsTransformer.cpp
- 列变换器语法定义:src/Parsers/ASTColumnsTransformers.h
- 字符串检索器:src/Common/StringSearcher.h
- CPU lease 分配器:src/Common/Scheduler/CPULeaseAllocation.cpp、src/Common/Scheduler/CPULeaseAllocation.h
- 工作负载资源管理器:src/Common/Scheduler/WorkloadResourceManager.cpp
- 调度器回归测试:src/Common/Scheduler/Nodes/tests/gtest_workload_resource_manager.cpp
- 官方变更记录原文:docs/changelogs/v26.2.15.4-stable.md
六、小结
v26.2.15.4-stable 是 ClickHouse 26.2 稳定分支上一个聚焦正确性与稳定性的补丁版本。三个修复分别触及查询改写、字符串检索与 CPU 资源调度三条核心路径,其中 CPU lease 调度器的 use-after-free 修复(以ThreadGroupPtr持有计数器所有权 + 成员声明顺序保证)是典型的生命周期管理教科书案例,值得资源调度类系统开发者借鉴。对于生产环境使用者,建议结合本文的触发场景与验证清单,评估并规划升级窗口。
【免费下载链接】ClickHouseClickHouse® is a real-time analytics database management system项目地址: https://gitcode.com/GitHub_Trending/cli/ClickHouse
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考