
ClickHouse v24.7.3.42-stable 补丁解析DDL 死锁、并行副本取消死锁与 JSONMergePatch 栈溢出修复【免费下载链接】ClickHouseClickHouse® is a real-time analytics database management system项目地址: https://gitcode.com/GitHub_Trending/cli/ClickHouse本文基于当前仓库中 v24.7.3.42-stable 变更日志逐条解读该稳定分支补丁63730bc4293相对上一版 v24.7.2.13-stable6e41f601b2f的全部修复内容并结合仓库源码剖析其中 DDL Worker 死锁、并行副本取消路径死锁、uniq/uniqTheta崩溃以及JSONMergePatch栈溢出防护的实现细节帮助运维与内核开发者准确评估这一 backport 补丁的影响面与升级价值。一、版本定位一次面向 24.7 稳定分支的缺陷修复发布从变更日志结构看v24.7.3.42-stable 是一次典型的 backport 补丁发布所有条目都标注了 Backported in #issue 的追溯号说明这些修复首先合入主干再被带回 24.7 LTS 稳定分支。条目分为三类Bug Fix官方稳定版本中用户可见的行为错误共 6 项Critical Bug Fix涉及崩溃、逻辑错误、数据丢失、RBAC 的严重缺陷共 4 项NOT FOR CHANGELOG / INSIGNIFICANT不面向用户的测试与 CI 修复共 9 项。这类发布不包含新功能风险极低是生产环境小步快跑升级的理想目标版本。下面按类别逐条展开。二、Bug Fix用户可见行为修复1. ALTER ADD COLUMN 后子列subcolumns读取错误修复点ALTER TABLE ... ADD COLUMN之后JSON 动态子列subcolumns的读取路径可能出现数据读取异常对应 PR #66243由 Anton Popov 提交。从源码结构看该修复落在动态 JSON 列的元数据演进逻辑上ClickHouse 会为JSON类型的动态字段建立 subcolumn 树而ALTER ADD COLUMN会新增一个子列节点旧数据部分没有该节点、新数据部分有——这正是 测试 03036_dynamic_read_subcolumns 中被拆分为慢测试来验证的场景见下文测试部分。如果你在线表上频繁做ALTER ADD COLUMN且列类型为JSON这条修复直接影响读数据正确性应作为升级的首要动因。2. Context::getDDLWorker 偶发死锁修复点Context::getDDLWorker存在偶发死锁对应 PR #66843由 Alexander Gololobov 提交。这是本补丁中最值得源码级细读的一条。查看当前仓库的 src/Interpreters/Context.cpp可以看到getDDLWorker的实现轮廓DDLWorker Context::getDDLWorker() const { // We have to ensure that DDL worker will not interfere with async loading of tables. // So we have to wait for all tables to be loaded before starting up DDL worker. if (shared-ddl_worker_startup_task) waitLoad(shared-ddl_worker_startup_task); { /// Only acquire the lock for reading ddl_worker field. /// hasZooKeeper() and hasDistributedDDL() acquire the same lock as well and /// double acquisition of the lock in shared mode can lead to a deadlock /// if an exclusive lock attempt is made in the meantime by another thread. SharedLockGuard lock(shared-mutex); if (shared-ddl_worker) return *shared-ddl_worker; } ... }注释精确指出了死锁成因hasZooKeeper()与hasDistributedDDL()也会拿同一把shared-mutex的读锁而getDDLWorker在持有读锁的状态下调用它们旧实现中形成读锁双重获取double acquisition in shared mode。此时若第三个线程尝试对该可升级锁请求独占锁会排队阻塞在锁队列前端而后两个线程又都在等读锁三者互相等待形成死锁。这就是注释中double acquisition of the lock in shared mode can lead to a deadlock if an exclusive lock attempt is made in the meantime的含义——典型的共享锁可升级锁升级饥饿问题。修复后的代码只把读锁范围缩小到仅读取ddl_worker字段如上面SharedLockGuard只包裹字段读取把hasZooKeeper()的调用移到锁外从而消除重入。该死锁的触发路径与 DDLWorker 相关模块紧密相连getDDLWorker被 executeDDLQueryOnCluster.cpp、DDLTask.cpp、InterpreterSystemQuery.cpp 等多处调用意味着任何走 ZooKeeper 分布式 DDL 队列的ON CLUSTER查询都可能触达此路径。3. 并行副本下取消查询的死锁修复点使用 parallel replicas并行副本时取消查询cancel query可能引发死锁对应 PR #66905由 Nikita Taranov 提交。并行副本是 ClickHouse 24.x 的重要读加速特性此修复保证了查询取消这一运维兜底操作在高并发读场景下不会把节点挂死。对使用parallel_replicas配置的集群这是稳定性刚需。4. 非归档文件名/URI 中::的解析错误修复点文件名或 URI 中含有::且并非归档archive时解析会出错对应 PR #67433由 Antonio Andelic 提交。背景是 ClickHouse 表函数与文件读取对压缩/归档格式采用archive::inner_path的语法约定例如s3(hdfs://path.tar.gz::file.csv)。此前实现过于激进地按::切分导致文件名本身带::如 S3 上的data::v2.json、HDFS 路径含该字符的非归档文件被误判为归档条目。此修复收紧了解析条件只有在确认为归档容器时才把::当分隔符。5. 含 UNION 的 SQL UDF 重载失效修复点使用UNION定义的 SQL UDFCREATE FUNCTION ... AS SELECT ... UNION SELECT ...在服务器重启重载后可能变为无效对应 PR #67665由 Antonio Andelic 提交。此前重启服务器会触发 UDF 重定义失败。这属于持久化 UDF 元数据重载路径的缺陷修复后基于 UNION 组合逻辑的用户自定义函数在重启后可正常恢复。6. ALTER ADD/MODIFY COLUMN 时校验实验性/可疑数据类型修复点ALTER ADD COLUMN/ALTER MODIFY COLUMN现在会校验实验性experimental与可疑suspicious数据类型对应 PR #67911由 Kruglov Pavel 提交。从变更日志看该条目与下文 Critical 类中 Variant 崩溃修复同属一个作者可以推断两者存在关联Variant是当时处于实验阶段的数据类型若允许用户随意在 DDL 上添加Variant或AggregateFunction等类型列会在后续读写路径触发崩溃。此修复把防线前移到 DDL 入口当目标数据类型需要实验性开关或已被标记为可疑时ALTER语句直接报错拦截而不是等到数据写入/读取时才崩溃。这是入口校验优于事后崩溃的典型工程决策。三、Critical Bug Fix崩溃类严重缺陷1. Variant AggregateFunction 类型组合导致崩溃修复点仅限实验性Variant数据类型与AggregateFunction类型组合使用时会崩溃对应 PR #67122。使用Variant的表或查询若涉及聚合列属于高风险路径未使用Variant的用户可直接忽略此条。2.uniq/uniqTheta传入tuple()空元组参数崩溃修复点uniq与uniqTheta聚合函数在参数为tuple()空元组时崩溃对应 PR #67306由 flynn 提交关闭 issue #67303。在仓库中uniqTheta的名称注册位于 src/AggregateFunctions/AggregateFunctionUniq.h/// uniqTheta ... static String getName() { return uniqTheta; }uniq系列是 HyperLogLog/Theta Sketch 家族的近似去重计数函数正常路径下参数为标量或元组列。传入tuple()这类空元组时聚合状态初始化阶段会按元组元素数0 个构造状态结构触发未定义行为导致进程崩溃。此修复属于对退化输入degenerate input的健壮性补齐。若你的查询生成器或 BI 工具可能产出uniq(tuple())这类空元组表达式升级到本版本可避免节点崩溃。3.ReplaceTableNodeToDummyVisitor越界遍历未解析的表函数参数修复点避免ReplaceTableNodeToDummyVisitor遍历未解析unresolved的表函数参数对应 PR #67522由 Dmitry Novik 提交修复 issue #66026。该访客类位于当前仓库的 src/Planner/findParallelReplicasQuery.cpp是一个查询树深度遍历器class ReplaceTableNodeToDummyVisitor : public InDepthQueryTreeVisitorWithContextReplaceTableNodeToDummyVisitor { using Base InDepthQueryTreeVisitorWithContextReplaceTableNodeToDummyVisitor; ...从类名与所在文件可以推断它服务于查找可并行副本化的查询路径需要在计划树上把表节点替换为 dummy 节点后重新遍历若查询中含尚未解析参数unresolved arguments的表函数如s3(...)、file(...)等延迟绑定参数旧代码会在遍历其参数子树时越界或崩溃。修复让该访客跳过未解析节点保证含表函数的复杂查询在并行副本分析阶段安全通过。4.JSONMergePatch潜在栈溢出与函数更名这是本补丁中实现细节最丰富的一条修复JSONMergePatch函数潜在栈溢出同时把函数名从不规范的jsonMergePatch更正为JSONMergePatch旧名保留以兼容并改进了错误诊断对应 PR #67756由 Alexey Milovidov 提交。当前仓库的 src/Functions/jsonMergePatch.cpp 展示了完整的防护设计与修复说明完全对应#if USE_RAPIDJSON /// Prevent stack overflow: #define RAPIDJSON_PARSE_DEFAULT_FLAGS (kParseIterativeFlag) ... /// Parsing is iterative (see RAPIDJSON_PARSE_DEFAULT_FLAGS above), but copying, merging and /// serializing a document all recurse over its tree, so a valid but deeply nested document /// would exhaust the thread stack. Reject such documents right after parsing; the check itself /// is iterative. constexpr size_t max_json_merge_patch_depth 1000; void checkJSONDepth(const TrackedValue root) { ... }源码揭示了两层防御解析层强制 rapidjson 使用kParseIterativeFlag迭代式解析使 parser 自身不递归恶意构造的深层嵌套 JSON 无法在解析阶段耗尽线程栈文档层解析虽已迭代化但拷贝、合并、序列化三个阶段仍然对文档树递归因此解析完成后立即调用checkJSONDepth做迭代式深度检查用显式to_visit栈实现见 checkJSONDepth 实现嵌套深度超过 1000 层直接抛出TOO_DEEP_RECURSION异常错误信息明确给出上限值。此外该文件还引入了一组RapidJSONMemoryTrackerAllocator包装类型TrackedPoolAllocator、TrackedDocument等把 rapidjson 的 DOM、解析栈与输出缓冲区全部计入 ClickHouse 的内存跟踪器——异常输入既不能爆栈也不能绕过max_memory_usage限制无界分配。这正是 changelog 中Improved diagnostic of errors in the function的底层支撑。四、NOT FOR CHANGELOG测试与 CI 修复9 项这部分不面向用户行为但反映了补丁的工程卫生状况值得简要列出以便核对完整性条目内容追溯 PR测试拆分拆分慢测试03036_dynamic_read_subcolumns#66954测试拆分拆分01508_partition_pruning_long#66983测试时限降低00763_long_lock_buffer_alter_destination_table最大耗时#67185集成测试修复易 flaky 的test_mask_sensitive_info#67506集成测试修复test_zookeeper_config_load_balancingxdist worker 名影响实例#67590无状态测试修复02434_cancel_insert_when_client_dies#67600无状态测试修复 fast test 中02910_bad_logs_level_in_local#67603无状态测试修复01605_adaptive_granularity_block_borders#67605长时测试尝试修复03143_asof_join_ddb_long回滚一个导致 count 非确定性的改动#67620 / #67800其中03036_dynamic_read_subcolumns的拆分与上文ALTER ADD COLUMN 后子列读取修复形成呼应该测试正是覆盖动态子列读取的长耗时用例。五、升级建议与适用边界适用对象正在运行 v24.7.2.13-stable 或更早 24.7.x 稳定版的集群。本补丁只含 backport 缺陷修复不含行为变更与新特性是低风险升级。优先级判断使用 ZooKeeper 分布式 DDLON CLUSTER且曾遇到节点无响应/线程 hang 的重点关注 getDDLWorker 死锁修复开启 parallel replicas 高并发读、依赖取消查询兜底的关注取消死锁修复使用实验性Variant或AggregateFunction列的关注崩溃修复与 DDL 入口校验大量使用JSONMergePatch处理不可信 JSON 输入的关注栈溢出与内存跟踪加固并可将旧函数名jsonMergePatch平滑迁移为JSONMergePatch旧名保持兼容。验证方式升级后除常规冒烟外可在只读副本上执行涉及 JSON 动态子列的ALTER ADD COLUMN再查询该子列验证第二条修复生效JSONMergePatch的防护可用一条超深嵌套 JSON超过 1000 层验证其返回TOO_DEEP_RECURSION而非进程崩溃。综上v24.7.3.42-stable 是一个小而准的稳定性补丁10 项用户可见修复覆盖了 DDL 并发、并行副本、SQL UDF 持久化、JSON 数据处理四条高危路径全部有明确的问题追溯号与源码级实现佐证见 变更日志原文。对 24.7 分支的生产集群建议尽快纳入升级窗口。【免费下载链接】ClickHouseClickHouse® is a real-time analytics database management system项目地址: https://gitcode.com/GitHub_Trending/cli/ClickHouse创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考