ClickHouse v23.3.21.26 LTS 补丁版本解读:六大用户可见缺陷修复与内部加固
【免费下载链接】ClickHouseClickHouse® is a real-time analytics database management system项目地址: https://gitcode.com/GitHub_Trending/cli/ClickHouse
导读:v23.3.21.26-lts 是 ClickHouse 23.3 LTS 分支上的一个维护性补丁版本,以修复"官方稳定版中的用户可见错误行为"为核心目标。本文逐一解析该版本 changelog 中记录的 6 个 Bug Fix 与 3 项内部改动,并结合当前仓库源码说明每处修复所涉及的模块、根因方向与影响范围,帮助维护者在升级 23.3 系列时快速评估风险并定位问题域。
版本概览:一次小而精的 LTS 补丁迭代
本版本的基础信息记录在 v23.3.21.26-lts changelog 中:
- 发布版本:
v23.3.21.26-lts(源码提交d9672a3731f) - 上游基线:相比
v23.3.20.27-lts(提交cc974ba4f81)的增量修复 - 版本类型:23.3 LTS(长期支持)分支的第 21 个补丁(patch)迭代,延续
v23.3.x.lts的偶数位补丁编号节奏
changelog 将改动分为两类:
| 分类 | 数量 | 说明 |
|---|---|---|
| Bug Fix(用户可见错误行为修复) | 6 | 官方稳定版中用户可观察到的行为缺陷,属于必须修复的范畴 |
| NOT FOR CHANGELOG / INSIGNIFICANT | 3 | 内部实现加固、测试基础设施调整,不直接改变用户可见行为 |
这种"主线功能冻结、只做缺陷修复与稳定性加固"的模式,正是 LTS 分支的核心运营策略:不引入新特性,专注让长期运行的生产集群更稳。
下面按模块逐条解析这 6 个 Bug Fix。
一、修复重启后读取稀疏列(sparse columns)的问题
修复条目:#49660,"Fix reading from sparse columns after restart"(Anton Popov)。
ClickHouse 的稀疏序列化(Sparse Serialization)是一种针对大量默认/零值列的存储优化:仅保存非默认值及其位置索引,显著降低数据占用。该功能的核心实现位于 SerializationSparse.cpp,其配套的类型信息管理在 SerializationInfo.cpp 与 SerializationInfoSettings.cpp 中。
该修复解决的是持久化元数据在进程重启后的恢复一致性问题:MergeTree 表的稀疏序列化选择(某列是否按稀疏方式存储)被记录在 parts 的元数据中。若读取路径在重启后未能正确恢复这一信息,后续读取就可能按错误的序列化格式解析数据,导致读取结果错误甚至数据损坏。修复后,重启场景下稀疏列读取能够稳定还原正确的序列化格式信息。
从测试覆盖看,仓库中的 gtest_sparse_serialization.cpp 对稀疏序列化的读写往返(round-trip)做了系统验证,与本修复的"读取一致性"目标直接对应。建议使用稀疏列优化(即SETTINGS ratio_of_defaults_for_sparse_serialization或显式SPARSE声明)的 23.3 用户优先升级到本版本。
二、修复 CompressionCodecMultiple 中的缓冲区溢出
修复条目:#60731,"Fix buffer overflow in CompressionCodecMultiple"(Alexey Milovidov)。
复合压缩编解码器(CompressionCodecMultiple)允许将多个 codec 串联成一个链,例如CODEC(ZSTD, LZ4)或自定义组合,是 ClickHouse 列式压缩中最灵活的机制之一。其实现位于 CompressionCodecMultiple.cpp,注册逻辑见同文件底部的registerCodecMultiple。
从当前源码可以清楚看到该 bug 修复留下的防御性逻辑:
- 4 GiB 回绕检测:
getCheckedReserveSize中,若"为压缩预留的大小"小于输入大小,说明 UInt32 算术发生了回绕(wrap),此时直接抛出CANNOT_COMPRESS异常,避免向过小的缓冲区写入数据(CompressionCodecMultiple.cpp)。 - 链长度上限检查:
getMaxCompressedDataSize中,codec 链的个数被存储在一个字节(UInt8)内,因此超过 255 个 codec 的链会被直接拒绝(CompressionCodecMultiple.cpp)。 - 压缩后大小再校验:
doCompressData在写完数据前再次核对written_size是否超出预留的dest_size,双保险防止越界(CompressionCodecMultiple.cpp)。
简而言之,本修复针对的是极端输入大小(接近 4 GiB 的数据块)配合多级 codec 链时,UInt32 预留大小计算溢出导致的缓冲区写入越界问题。受影响面主要是启用多级 codec 链且处理超大块数据的场景。相关单元测试见 gtest_compressionCodec.cpp 与 gtest_compression_codec_adaptive.cpp。
三、清理 SQL/JSON 函数中的不合理行为
修复条目:#60738,"Remove nonsense from SQL/JSON"(Alexey Milovidov)。
这里的 "nonsense" 指的是 SQL/JSON 路径函数族(如JSON_EXISTS、JSON_VALUE、JSON_QUERY,以及json相关变体)中存在的不合理语义或死代码行为。SQL/JSON 支持的核心实现在 FunctionSQLJSON.cpp 与其头文件 FunctionSQLJSON.h 中,底层依赖JSONPath解析器(ParserJSONPath.h)和 JSON 解析后端(simdjson 等,见 SimdJSONParser.h)。
从源码可以确认,SQL/JSON 函数族包含多个可通过设置项调节的行为:
function_json_value_return_type_allow_complex/function_json_value_return_type_allow_nullable:控制JSON_VALUE返回类型的推导约束;max_parser_backtracks/max_parser_depth:限制 JSONPath 解析器的回溯次数与深度,防止恶意/畸形路径导致资源耗尽(FunctionSQLJSON.h)。
该修复属于"行为收敛"类改动——删除无意义的语义,使函数行为更符合 SQL 标准预期。使用 SQL/JSON 函数的用户在升级后应重新验证相关查询的结果一致性,特别是依赖旧版非标准行为的场景。
四、修复 arrayEnumerateRanked 的崩溃
修复条目:#60764,"Fix crash in arrayEnumerateRanked"(Raúl Marín)。
arrayEnumerateRanked家族函数(arrayEnumerateRanked、arrayEnumerateUniqRanked、arrayEnumerateDenseRanked)用于对数组元素按排名(rank)进行枚举,常用于数组内去重统计、Top-N 分析。其实现位于 arrayEnumerateRanked.cpp 及头文件 arrayEnumerateRanked.h。
从当前源码可以看出该函数族对参数校验的严格要求,这正是本次崩溃修复的上下文:
- 第一个参数若是非数组,则必须是
Const(UInt64)且为正整数,否则抛出BAD_ARGUMENTS; - 后续参数须以"数组 + 可选的常量深度"成对出现,深度值不能超过对应数组的实际维度;
- 全局
clear_depth不能大于所有数组的最大深度(arrayEnumerateRanked.cpp)。
本修复解决的是特定参数组合(如多数组 + 自定义深度 + 复杂键列)触发未定义行为导致进程崩溃的问题。崩溃类缺陷的危害在于可能导致单次查询把整个 server 进程带崩(而非仅报错),因此升级收益明确。使用该函数族的分析型查询(如商品曝光去重排名)应回归测试。
五、修复 INSERT SELECT JOIN 中使用 input() 的崩溃
修复条目:#60765,"Fix crash when using input() in INSERT SELECT JOIN"(Kruglov Pavel)。
input()是一个特殊函数,用于在INSERT INTO ... SELECT ...场景中直接引用"正在从客户端流入的原始输入块",是高性能导入管线的常用技巧(例如配合FORMAT做流式清洗)。它与JOIN组合时,input()所代表的数据流需要作为查询计划中的一个特殊数据源节点参与执行。
本修复解决的是在INSERT INTO t SELECT ... FROM input() JOIN ...这类语句中,执行计划生成阶段对input()数据源与 JOIN 的交互处理不当而触发的崩溃。从仓库看,这类"运行时数据源"在查询计划中通常对应 Processors 执行图里的特殊 Source 节点(可参考 PipelineExecutor.cpp 的执行模型)。建议所有在导入管线中使用input()+JOIN组合的用户升级本版本。
六、移除读取 S3 数据时的递归
修复条目:#60849,"Remove recursion when reading from S3"(Antonio Andelic)。
S3 表引擎与 S3 磁盘是 ClickHouse 云原生存储的核心能力。在对象存储(S3)场景中,读取目录/前缀下大量对象时,若代码以递归函数调用的方式逐层遍历对象列表,当对象数量极大(成千上万级)或嵌套层级过深时,会耗尽进程栈空间导致栈溢出崩溃。
本修复将读取路径中的递归遍历改为显式的迭代式遍历(如使用显式栈/队列),从而把"数据量大"与"调用栈深度"解耦。这类改动不影响用户可见语义,但显著提升了海量小文件场景(如日志类分区、parquet/ORC 文件列表)下的稳定性与内存行为。使用s3://表函数、S3 表引擎或 S3 备份的 23.3 用户尤其值得升级。
内部加固:不进 changelog 的稳定性投入
除用户可见修复外,该版本还包含 3 项标记为 "NOT FOR CHANGELOG / INSIGNIFICANT" 的内部改动,它们同样值得了解:
1. 异常时正确取消 PipelineExecutor 的线程派生(#57104 / #60499)
执行器(PipelineExecutor)负责将查询计划编译成 Processor 流水线并调度线程执行,核心实现位于 PipelineExecutor.cpp 与 ExecutorTasks.cpp。该修复(含一次重复提交)确保在spawnThreads阶段抛出异常时,已派生的线程能被正确取消与回收,避免线程泄漏、任务悬挂或查询结束后残留线程继续运行。
2. 在测试中检测 io_uring(#60373)
io_uring 是 Linux 的高性能异步 I/O 接口,ClickHouse 在某些平台/配置下会启用其支持。该改动让测试环境能自动检测 io_uring 的可用性,从而在 CI 中正确选择异步 I/O 后端与对应断言,避免因平台差异导致误报。
升级建议与验证方式
综合以上分析,v23.3.21.26-lts是一次典型的 LTS 缺陷收敛版本:
- 强烈建议升级:使用稀疏列、复合 CODEC 链、S3 存储、
arrayEnumerateRanked或input()+ JOIN 导入管线的生产集群; - 回归重点:重启后稀疏表读取、多级压缩链的大块数据写入、SQL/JSON 查询结果、数组排名函数查询、S3 海量对象读取;
- 变更面小:本版本不含新特性与破坏性改动,6 个 Bug Fix 均为局部模块修复,升级风险可控。
升级后可用SELECT version()确认版本号,并通过system.query_log观察上述相关查询的执行状态。若需追溯更早的 23.3 LTS 演进历史,可浏览 changelogs 归档目录;对 23.3 分支更早期补丁的对比,可参考同目录下v23.3.20.27-lts等相邻版本的 changelog 文件。
【免费下载链接】ClickHouseClickHouse® is a real-time analytics database management system项目地址: https://gitcode.com/GitHub_Trending/cli/ClickHouse
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考