
ClickHouse v20.8.16.20-lts 修复解析常量条件下的分片跳过与 CatBoost 模型加载问题【免费下载链接】ClickHouseClickHouse® is a real-time analytics database management system项目地址: https://gitcode.com/GitHub_Trending/cli/ClickHouse导读本文以 ClickHouse v20.8 LTS 分支的补丁版本 v20.8.16.20-lts 发布记录docs/changelogs/archive/v20.8.16.20-lts.md为骨架深度解析其中两项 Bug Fix 背后的分布式查询分片跳过optimize_skip_unused_shards机制与 CatBoost 机器学习模型集成设计。读完你将掌握分片跳过优化器的完整参数体系、触发条件与规避策略并了解 CatBoost 集成在 ClickHouse 中的演进脉络与兼容处理方式。版本背景v20.8 LTS 分支的一次补丁迭代v20.8.16.20-lts是 ClickHouse v20.8 LTSLong-Term Support系列中的一个补丁版本相较上一补丁版本v20.8.15.11-lts本次仅包含两项Bug Fix没有任何新特性或性能改进。这类小而精的补丁发布正是 LTS 分支的典型节奏在功能冻结的前提下持续回移backport社区在主干上修复的缺陷保证长期维护用户可以以较低风险获得稳定性修复。本次发布记录中的两项修复分别指向两个截然不同的领域修复关联 PR影响领域常量WHERE条件下所有分片可能被跳过导致返回错误的空结果PR #21550经 #22091 回移分布式查询优化optimize_skip_unused_shards首次执行 CatBoost 模型时发生死锁PR #21844经 #22049 回移修复 #13832机器学习模型集成catboostEvaluate等函数下文分别深入这两项修复的技术原理。修复一常量WHERE条件下所有分片被错误跳过故障现象修复前的行为是当查询带有常量WHERE条件且开启了设置optimize_skip_unused_shards时所有分片都可能被跳过查询返回一个不正确的空结果。典型场景如下——对分布式表执行恒假条件类查询例如WHERE 0、WHERE 1 2、WHERE shard_key NOT IN (…)等时ClickHouse 的分片跳过逻辑会把没有任何分片需要访问误解为优化机会直接跳过全部分片从而返回空集而非真实结果。如果常量条件实际上是有意义的例如经过参数替换、宏展开或视图改写后变成常量的条件这种错误空结果会造成严重的数据正确性问题。为什么会出错常量条件求值与分片选择的交互要理解该缺陷需要先看optimize_skip_unused_shards的工作方式。该优化src/Core/Settings.cpp 中的定义Enables or disables skipping of unused shards for SELECT queries that have sharding key condition in WHERE/PREWHERE, and activates related optimizations for distributed queries (e.g. aggregation by sharding key).其核心思想是如果查询的WHERE/PREWHERE中包含分片键sharding key上的过滤条件那么可以只把查询发给与条件匹配的分片而不是广播到全部分片从而显著减少跨节点网络开销与远端计算量。实现上ClickHouse 会对条件表达式做常量折叠replaceConstantExpressions再基于分片键表达式对折叠后的条件求值得到一个该条件命中的分片集合。当条件本身是常量如WHERE 0时折叠后的表达式不依赖任何输入列evaluateExpressionOverConstantCondition可以直接在不访问任何数据的前提下给出结论。修复前逻辑的缺陷在于对常量条件命中零个分片这一边界情况的处理不够严谨——所有分片都被标记为未使用从而被跳过最终返回错误的空结果。底层实现skipUnusedShards 的求值流程当前仓库中这一优化在 src/Storages/StorageDistributed.cpp 的StorageDistributed::skipUnusedShards中实现其完整流程为前置检查若查询既无PREWHERE也无WHERE直接返回nullptr不跳过任何分片。剔除 JOIN克隆查询并调用removeJoin移除 JOIN 部分因为 JOIN 中可能包含其他表的条件只有针对左侧分布式表的条件才应参与分片跳过分析。条件合并与常量替换将PREWHERE与WHERE用and合并为单一条件随后调用replaceConstantExpressions对条件做常量折叠。常量条件求值调用evaluateExpressionOverConstantCondition(condition_ast, sharding_key_expr, limit)在单行数据上下文中基于分片键表达式对条件求值得到命中的分片键值块blocks。结果判定求值块为空无法得到确定答案→ 返回nullptr不做跳过保守策略保证正确性优先求值块非空 → 遍历每个块调用createSelector(cluster, result)把分片键值映射为具体的分片编号得到需要访问的分片集合据此生成裁剪后的ClusterPtr。值得注意的是代码中有明确的if (!blocks) return nullptr;分支——无法得到确定答案时宁可不优化。本次修复的实质就是确保**常量条件确定命中零个分片与条件无法求值这两种情况被正确区分**前者应返回真实的空结果而不是因为跳过所有分片而伪造的空结果后者应保守地回退到广播查询。这种优化不得改变语义的原则也是后续所有分片跳过相关改动必须守住的红线。optimize_skip_unused_shards 系列设置完整参考围绕分片跳过ClickHouse 提供了一整套相互配合的设置全部定义在 src/Core/Settings.cpp。理解这套参数是正确使用该优化、规避本次修复所针对问题的基础设置名类型默认值说明optimize_skip_unused_shardsBoolfalse总开关。开启后对WHERE/PREWHERE中带分片键条件的 SELECT 跳过未使用分片并激活相关分布式优化如按分片键聚合。前提假设数据确实按分片键分布否则查询会返回错误结果optimize_skip_unused_shards_limitUInt641000分片键值数量的上限。若IN (...)等条件中的分片键值数超过该上限则自动关闭分片跳过。值过多时处理代价大而收益存疑——反正查询大概率会发给所有分片optimize_skip_unused_shards_rewrite_inBooltrue对远端分片重写IN子句剔除不属于该分片的值依赖optimize_skip_unused_shards开启allow_nondeterministic_optimize_skip_unused_shardsBoolfalse是否允许分片键中使用非确定性函数如rand、dictGet——后者因存在更新语义有坑。关闭时仅对确定性分片键启用跳过force_optimize_skip_unused_shardsUInt640当无法跳过未使用分片时是否拒绝执行查询0不抛异常1仅在表定义了分片键时禁用查询2无论表是否定义分片键都禁用查询并抛异常optimize_skip_unused_shards_nestingUInt640控制分片跳过生效的分布式查询嵌套层级Distributed表套Distributed表的场景0始终生效1仅第一层2至第二层。仍要求optimize_skip_unused_shards开启force_optimize_skip_unused_shards_nestingUInt640与上同理控制force_optimize_skip_unused_shards的嵌套层级0始终生效1仅第一层2至第二层规避与最佳实践结合本次修复与上述参数语义使用分片跳过时的实践要点如下默认关闭是安全的optimize_skip_unused_shards默认值为false。只有当数据确实通过 Distributed 表按sharding_key分布写入时才建议开启否则会得到错误结果设置注释中对此有明确警告。用force_optimize_skip_unused_shards做正确性兜底在关键业务集群上可将其设为1或2让无法确定性地跳过分片这一情况直接抛异常从而把本次修复针对的错误空结果暴露为显式错误而不是静默返回错误数据SET optimize_skip_unused_shards 1; SET force_optimize_skip_unused_shards 2; -- 无法跳过时直接报错而非返回错误结果警惕常量条件经过宏替换、参数化或视图展开后形成常量条件的查询是本次修复的核心场景。升级到包含修复的版本后这类查询会返回与直接在各分片本地执行一致的真实结果。合理设置optimize_skip_unused_shards_limit当IN (...)中的分片键值数量超过限制时优化会被静默关闭日志会提示Number of values for sharding key exceeds optimize_skip_unused_shards_limit...见 src/Storages/StorageDistributed.cpp。盲目调大该值会拖慢查询分析阶段需权衡收益。嵌套场景用 nesting 参数精确控制多级Distributed表的场景下用optimize_skip_unused_shards_nesting/force_optimize_skip_unused_shards_nesting限定生效层级避免外层裁剪与内层实际数据分布不一致。修复二CatBoost 模型首次执行死锁故障现象与根因第二个修复针对的问题是首次执行 CatBoost 模型加载模型并调用模型评估函数时发生死锁该问题最初由 issue #13832 报告由 PR #21844 修复并经 #22049 回移到 v20.8 LTS 分支。死锁的典型背景是ClickHouse 的 CatBoost 集成允许用户把训练好的 CatBoost 模型.bin文件注册为外部模型并通过catboostEvaluate系列函数在 SQL 中直接进行模型推理。模型在首次被查询引用时才惰性加载而首次加载需要同时完成读取模型文件 初始化评估库 编译/加载运行时代码等多步操作。当并发请求同时触发首次加载、或加载路径与查询执行路径对同一把锁/同一资源产生竞争时就可能形成死锁。虽然 v20.8 时代的该集成代码在当前仓库主干中已不可见见下文现状小节但惰性初始化 并发首触这一经典竞态模式仍是理解该修复的关键修复的方向通常是在加载路径上保证初始化只发生一次且全程加锁顺序一致避免部分初始化状态下被其他线程观测到。CatBoost 在 ClickHouse 中的集成方式源码残留证据尽管 CatBoost 集成代码已从当前主干移除仓库中仍保留多处与该集成直接相关的痕迹可作为理解其历史设计的证据专属错误码src/Common/ErrorCodes.cpp 定义了CANNOT_LOAD_CATBOOST_MODEL错误码 382模型加载失败与CANNOT_APPLY_CATBOOST_MODEL错误码 383模型应用失败两个独立错误码说明该集成具备完整的加载—应用两阶段错误分类便于用户区分模型文件问题与推理执行问题。服务端配置占位src/Core/ServerSettings.cpp 中的注释明确指出 The CatBoost integration is removed, but a leftovercatboost_lib_pathmust not prevent the server from starting.——即旧的catboost_lib_path指向 CatBoost 动态库路径的服务端配置项虽已被移除但解析逻辑仍会容忍该遗留配置保证老用户升级后服务器可以正常启动不会被残留配置卡住。权限兼容占位src/Access/Common/AccessType.h 保留了与 CatBoost 相关的访问权限类型占位注释说明其目的是让升级前已授予的权限在升级后仍可解析且不改变其他权限类型的数值编号避免破坏system.privileges/system.grants的既有数据。这三个痕迹清晰呈现了 ClickHouse 对移除一项功能的工程化处理错误码保留兼容监控与排查脚本、配置项宽容解析兼容老配置文件、权限类型占位兼容已授权数据全程把升级破坏面降到最低。现状CatBoost 集成移除后的兼容处理从当前仓库源码可以推断CatBoost 集成已从主干移除catboostEvaluate等函数在当前版本中不再可用。但上述兼容处理意味着若你在旧版本如 v20.8 时代配置过catboost_lib_path或授予过相关权限升级到移除该集成的版本不会导致启动失败或权限解析异常相关错误码CANNOT_LOAD_CATBOOST_MODEL382与CANNOT_APPLY_CATBOOST_MODEL383仍被保留若旧监控/告警脚本按错误码归类无需修改即可继续工作。对于仍在 v20.8 LTS 分支上使用 CatBoost 建模的用户本次修复消除了首次执行死锁的稳定性隐患对于计划升级到新版本的用户则应将模型推理逻辑从 ClickHouse 内迁移到外部推理服务并清理相关配置。升级与验证建议升级路径v20.8 系列的维护用户应升级到v20.8.16.20-lts或更高补丁版本。LTS 分支的补丁发布仅包含修复行为变化风险低。修复一验证升级后执行带常量条件的分布式查询如SELECT count() FROM distributed_table WHERE 0、WHERE 1 2或由视图/宏展开产生的常量条件核对结果与各分片本地执行结果一致确认不再出现错误的空结果。修复二验证若仍在使用 CatBoost 模型可在低峰期执行一次模型推理查询观察首次加载是否正常完成、并发下是否死锁同时确认catboost_lib_path等旧配置不再导致告警。回归检查对开启了optimize_skip_unused_shards的生产查询对比升级前后结果集与system.query_log中实际触达的分片数量确认优化行为符合预期且结果未变。总结v20.8.16.20-lts作为一个典型的 LTS 补丁版本用两项修复覆盖了 ClickHouse 两个反差极大的领域一端是分布式查询优化器里常量条件求值这类精细的语义边界——优化必须在任何时候都不得改变查询结果拿不准时就保守回退另一端是机器学习集成中惰性加载与并发这类经典的并发缺陷——首触初始化必须做到线程安全。理解前者需要掌握 src/Core/Settings.cpp 中一整套分片跳过参数的语义与默认值理解后者可以从 src/Common/ErrorCodes.cpp、src/Core/ServerSettings.cpp 与 src/Access/Common/AccessType.h 中看到 ClickHouse 在功能演进中兼顾兼容性的工程细节。对维护者而言这两类经验——优化不许破坏语义与移除功能也要平滑过渡——同样适用于自己负责的分布式系统与平台建设。【免费下载链接】ClickHouseClickHouse® is a real-time analytics database management system项目地址: https://gitcode.com/GitHub_Trending/cli/ClickHouse创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考