ClickHouse v26.2.15.4-stable 补丁版本深度解读:三大稳定性修复的源码级分析
2026/9/19 17:02:17 网站建设 项目流程

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 修复:ASTColumnsExceptTransformerLOGICAL_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)的核心流程是:

  1. 若未指定正则模式,则把EXCEPT列表中的子节点(标识符或其别名/列名)收集进expected_columns
  2. 遍历当前节点列表,将名称命中的列移除;
  3. 若声明了EXCEPT STRICTtransformer.is_strict)且仍有未匹配到的列,则抛出NO_SUCH_COLUMN_IN_TABLE异常,提示"Columns transformer EXCEPT expects following column(s) :..."
  4. 若指定了正则模式,则通过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 字符串检索基础设施的一部分,被positionlikestartsWith等高频文本函数使用。文件头部注释明确说明:这类检索器在多数场景下性能低于 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)的一部分,相关组件包括CPULeaseAllocationCPUSlotsAllocationWorkloadResourceManager等。

缺陷描述为:当等待定时器(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;

即:不再捕获「调用线程」的计数器,而是捕获查询所属 ThreadGroupThreadGroupPtr,一个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;

两个关键点:

  1. wait_thread_group声明在wait_timer之前——C++ 成员按声明顺序析构,因此ThreadGroupPtr(计数器所有者)一定比定时器晚销毁,即使析构路径上也安全;
  2. 兜底策略:当线程组不可用时(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),仅供参考

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

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

立即咨询