ClickHouse v21.6.7.57-stable 补丁版本解析:pointInPolygon 校验开关与后台任务退避修复
【免费下载链接】ClickHouseClickHouse® is a real-time analytics database management system项目地址: https://gitcode.com/GitHub_Trending/cli/ClickHouse
v21.6.7.57-stable 是 ClickHouse 21.6 稳定分支上的一个维护性补丁版本,它不引入新功能,而是集中修复了ALTER MODIFY COLUMN与 TTL 表达式的交互、后台任务池满载时的退避异常、右子查询 JOIN 的线程估算,以及pointInPolygon在关闭多边形校验时的崩溃风险。本文以仓库中的 v21.6.7.57-stable 变更日志 为骨架,结合当前仓库源码逐条拆解每个修复背后的实现原理、触发场景与验证方法,帮助你理解"补丁版本到底修了什么、为什么这么修"。
版本基线:FIXME 标记与 21.6 分支的维护节奏
该变更日志标题写为ClickHouse release v21.6.7.57-stable FIXME as compared to v21.6.6.51-stable。其中FIXME是 ClickHouse 变更日志生成流程中常见的占位标记:正式发布时该位置通常会被替换为与前一个版本的完整对比链接,这里保留原样说明该文档是发布流程中的中间产物,但其记录的修复内容本身是确定的。
从版本号可以读出两条信息:
- 基线版本:v21.6.7.57-stable 是 21.6 系列的第 7 个补丁版本,对比对象是 v21.6.6.51-stable;
- 发布性质:21.6 属于 ClickHouse 的 LTS(长期支持)稳定分支,补丁版本只合入经过回归验证的 Bug Fix,不会携带破坏性变更,这也决定了本日志中所有条目都属于
Bug Fix类别。
修复一:ALTER MODIFY COLUMN 与 TTL 表达式的兼容性问题
问题现象
第一个修复对应 PR #25554(由 Anton Popov 提交):
Fix
ALTER MODIFY COLUMNof columns, which participates in TTL expressions.
即:当某列参与了表的 TTL 表达式(行级 TTL、列级 TTL 或 TTL 用于删除/移动数据)时,对该列执行ALTER MODIFY COLUMN(修改列类型或参数)会触发问题。TTL 表达式会被持久化在表元数据中,并在后台合并(merge)任务执行时被重新解析求值;若列定义被修改而 TTL 表达式未同步重写,就会导致元数据解析失败或行为不一致。
实现原理
在 MergeTree 家族引擎中,TTL 信息(MergeTreeData::TTLInfo、TTLDescription)以及由 TTL 派生出的表达式(例如TTL_datetime + INTERVAL 1 DAY)是随表结构一起存储的。ALTER MODIFY COLUMN通过InterpreterAlterQuery进入MergeTreeData::alterTable流程,系统需要同时更新列定义与所有依赖该列的 TTL 表达式。
从当前仓库源码结构看,这一修复的落点在元数据更新链路:MergeTree 引擎的 alterTable 实现 负责校验列变更的兼容性,而 TTL 表达式的校验与重建涉及 src/Storages/MergeTree/MergeTreeData.h 中 TTL 描述的解析逻辑。修复的核心是:当列类型发生变化时,重新解析并校验涉及该列的 TTL 表达式,确保元数据中记录的 TTL 与新列定义保持自洽,避免后台任务按旧元数据执行时出错。
实际影响与建议
- 如果业务中频繁使用
ALTER TABLE ... MODIFY COLUMN调整列类型(如将DateTime列扩容、修改String列的LowCardinality包装),且该列同时出现在TTL子句中,建议升级到该补丁版本; - 升级后执行列类型变更时,应同时在测试环境验证相关 TTL 仍能正常求值(例如通过
SELECT观察 TTL 列的值变化,或触发一次 merge 观察后台任务日志)。
修复二:后台任务池满载时的"极长退避"问题
问题现象
第二个修复对应 PR #25893(alesapin),修复 issue #25836:
Fix extremely long backoff for background tasks when the background pool is full.
MergeTree 家族引擎依赖后台任务池(BackgroundJobsExecutor)执行 merge、mutation、分区删除(TTL 触发的DELETE/MOVE)等任务。当任务池满载(达到background_pool_size/background_merges_mutations_concurrency_ratio配置的上限)时,新任务需要排队等待;此前的实现会在任务反复失败或反复入队失败时计算指数退避(backoff),但在某些满载场景下退避时间会被计算得异常长,导致原本应该尽快重试的任务被挂起过久,表现为:
- merge 长时间不推进;
- 大量分区堆积;
- 磁盘空间告警后 TTL 清理迟迟不执行。
实现原理
后台任务调度的核心位于 src/Storages/MergeTree/BackgroundJobsExecutor.cpp。该组件维护一张任务表(task_storage),周期性轮询所有表并挑选"可执行"的任务交给线程池。退避逻辑(sleeptime/increaseSleepTime之类)本意是:当某张表的任务持续不可执行(例如该表正在执行多个 merge 已达并发上限)时,降低对该表的轮询频率,避免空转。
修复前的缺陷在于:当整个后台池满载时,任务不可执行的原因不是"该表有问题",而是"系统没有空闲线程",此时仍按指数增长退避时间,就会把"系统繁忙"误判为"该表故障",把睡眠时间推到数十分钟甚至更久。修复方向是区分"表自身不可执行"与"全局线程不足"两种状态,在全局繁忙时避免指数放大退避间隔。
相关配置
background_pool_size:后台任务池线程数上限,默认 16;background_merges_mutations_concurrency_ratio:merge/mutation 任务在线程池中的占比(默认 0.5,即一半线程用于 merge/mutation);background_schedule_pool_size等:与调度相关的线程配置。
当系统出现"merge 停滞 + 后台日志长时间无输出"时,除了排查退避问题,还应检查这些配置是否被调得过小。
修复三:右子查询 JOIN 的线程数估算错误
问题现象
第三个修复对应 PR #26052(Vladimir C),关闭 issue #24075:
Fix wrong thread estimation for right subquery join in some cases.
即:在RIGHT JOIN或RIGHT ANY JOIN场景下,当右表是子查询时,查询计划对"右子查询需要多少线程"的估算在部分场景下是错误的,可能引发资源浪费或调度失衡。
实现原理
从源码结构看,JOIN 的线程规划在查询流水线构建阶段完成:Join相关类位于 src/Interpreters/Join,而"每个 Join 步骤分配多少线程"取决于QueryPlan中各节点的updateProgress与并行度(degree)计算。右连接(RightJoin)在语义上要求以右表为驱动表,其子查询的输出行数、过滤选择性直接影响整体并行度分配;当右子查询带有聚合、LIMIT或复杂过滤时,行数估算偏差会被放大,导致线程分配过多或过少。
修复的核心是修正右子查询场景下的行数/线程估算路径,使得流水线的并行度与实际数据量匹配。这在当前代码中对应QueryPipeline构建时对JoinStep的 degree 决策逻辑(参见 src/Processors/QueryPlan 与 src/QueryPipeline)。
影响场景
- 高频使用
RIGHT JOIN (SELECT ...) AS r ON ...的分析查询; - 右子查询本身包含聚合或高选择性过滤;
- 需要稳定的执行计划与线程利用率。
如果你在 21.6.x 上观察到右连接查询偶发执行缓慢或 CPU 使用异常,可关注本修复。
修复四:pointInPolygon 关闭校验后的崩溃风险
问题现象
第四个修复对应 PR #26113(Alexey Milovidov):
Fix possible crash in
pointInPolygonif the settingvalidate_polygonsis turned off.
即:当服务器/会话设置validate_polygons = 0时,pointInPolygon函数在特定输入下存在崩溃风险。该修复是本次日志中唯一附带了完整底层实现可考证的条目,下面结合当前仓库源码深入展开。
pointInPolygon 是什么
pointInPolygon是 ClickHouse 的地理/几何函数族(Geo 类别)之一,用于判断平面上的点是否位于多边形内部,返回UInt8:1表示在内部,0表示不在。其注册与完整文档位于 src/Functions/pointInPolygon.cpp,支持四种调用形式(见源码getReturnTypeImpl中的注释):
-- 简单多边形(无洞) pointInPolygon((x, y), [(x1, y1), (x2, y2), ...]) -- 带洞多边形:每个洞作为后续独立参数 pointInPolygon((x, y), [(x1, y1), ...], [(h1x, h1y), ...], ...) -- 带洞多边形:所有环合并为一个二维数组 pointInPolygon((x, y), [[(x1, y1), ...], [(h1x, h1y), ...], ...]) -- 多多边形(MultiPolygon):三维数组或逐参数传入 pointInPolygon((x, y), [[[...], ...], ...])参数要求:第一个参数是(x, y)坐标元组,多边形参数必须是数值元组的(嵌套)数组,坐标内部会统一转换为Float64(CoordinateType = Float64)参与计算。
validate_polygons 设置的作用
validate_polygons是一个会话级布尔设置,默认值为true,其定义位于 src/Core/Settings.cpp#L5363:
0— 关闭校验,pointInPolygon接受无效多边形,可能返回不正确的结果;1(默认)— 开启校验,遇到自相交、自相切的多边形时抛出异常。
在函数实现中(src/Functions/pointInPolygon.cpp),该设置通过FunctionPointInPolygon::create读取context->getSettingsRef()[Setting::validate_polygons]传入构造函数,并存储为成员bool validate。当多边形为常量参数时,parseConstPolygon/parseConstMultiPolygon在解析后会调用 boost.geometry 的bg::is_valid做完整性校验(validate为true时校验失败会抛出BAD_ARGUMENTS异常,见 pointInPolygon.cpp 的 parseConstPolygon)。v21.6.7.57 之前的缺陷是:关闭校验后,某些畸形输入(例如环顶点过少、坐标值溢出、退化几何)会直接进入后续的网格(grid)预处理或 R-tree 匹配流程,在无保护的前提下触发未定义行为乃至崩溃。
崩溃修复与两条执行路径
源码清晰展示了pointInPolygon的两类执行路径:
- 常量多边形路径(预处理 + 缓存):当多边形是常量(
poly_is_const)时,会被预处理器PointInPolygonWithGridF64/PointInMultiPolygonRTreeWithGrid转成网格/R-tree 结构以加速匹配,预处理结果按常量参数的 XXH3 哈希(hashConstPolygonArguments)缓存在PreprocessedPolygonsCache中,缓存上限由服务器设置point_in_polygon_cache_size控制(默认 256 MiB,见 src/Core/Defines.h#L137 与 src/Core/ServerSettings.cpp#L751)。解析与校验只发生在缓存未命中时,因此validate_polygons标志也被纳入缓存键,避免用关闭校验时构建的条目满足开启校验的查询; - 非常量多边形路径(逐行射线法):当多边形来自表列时,使用 W. Randolph Franklin 的射线投射(pnpoly)算法逐行判定,见
isInsideRing的实现注释与代码(src/Functions/pointInPolygon.cpp#L565-L629)。该算法对顶点数小于 2 的环直接返回false,并在解析常量环时通过common::mulOverflow检查坐标乘积溢出。
v21.6.7.57 的修复要点(从源码可以确认):在validate_polygons = 0时,确保进入预处理流程的几何仍然满足基本的结构约束(至少 3 个顶点、坐标可参与后续计算),从而封堵崩溃路径。
验证与测试用例
仓库中pointInPolygon的无状态测试覆盖了上述两类路径与各种几何形态:
- tests/queries/0_stateless/00500_point_in_polygon.sql —— 覆盖简单多边形、带洞多边形、多多边形、逆序顶点、自相交多边形(默认校验下期望
BAD_ARGUMENTS异常)、常量/非常量多边形混合等场景; - 对应的期望输出 tests/queries/0_stateless/00500_point_in_polygon.reference 中包含了 "inner/outer"、"polygon with holes"、"polygons with reversed direction"、"multipolygon with one/two polygons with holes" 等测试分组的结果;
- 更多常量化测试见 tests/queries/0_stateless/00500_point_in_polygon_2d_const.sql。
可以通过如下方式复现默认行为(关闭校验前后的差异):
-- 默认 validate_polygons = 1:自相交多边形会抛 BAD_ARGUMENTS SELECT pointInPolygon((0.1, 0.1), [(0.5, 0.), (1.0, 0.), (8.0, 7.5), (7.5, 8.0), (0., 1.), (0., 0.5), (4.5, 5.5), (5.5, 4.5), (0.5, 0.)]); -- 关闭校验后不会抛异常,但结果可能不正确 SET validate_polygons = 0; SELECT pointInPolygon((0.1, 0.1), [(0.5, 0.), (1.0, 0.), (8.0, 7.5), (7.5, 8.0), (0., 1.), (0., 0.5), (4.5, 5.5), (5.5, 4.5), (0.5, 0.)]);运维建议
- 除非有明确的性能诉求且数据源保证多边形合法,否则建议保持
validate_polygons = 1(默认值),以让非法几何在查询期被显式拒绝,而不是得到不确定结果; - 若因历史数据原因必须关闭校验,请升级到包含 v21.6.7.57(或更新稳定版)的版本,避免触发此崩溃路径;
- 涉及大量常量多边形的查询可关注
point_in_polygon_cache_size(服务器设置,可在运行时修改并立即生效),并可用SYSTEM DROP POINT IN POLYGON CACHE手动清空缓存而不改变大小上限。
NOT FOR CHANGELOG 条目:21.6 手动回移植
日志末尾有一条NOT FOR CHANGELOG / INSIGNIFICANT条目:
21.6 manual backport of #25970 #26138
这是将主分支(master)上的 PR #26138 手动回移植到 21.6 分支的说明。在 ClickHouse 的发布流程中,凡是进入稳定分支的改动都必须经过"backport"流程(自动化或手动);标记为 NOT FOR CHANGELOG 表示该改动属于内部流程性质(如构建/测试基础设施修复),不会对用户可见行为产生直接影响,因此不进入正式变更日志正文。
总结与升级建议
v21.6.7.57-stable 是一个"小而准"的稳定分支补丁,四个 Bug Fix 分别落在:
| 修复 | 涉及模块 | 对用户的影响 |
|---|---|---|
| ALTER MODIFY COLUMN 与 TTL 列交互 | MergeTree 元数据(MergeTreeData.cpp) | 避免修改 TTL 列类型时元数据不一致 |
| 后台任务池满载的极长退避 | 后台调度(BackgroundJobsExecutor.cpp) | 避免 merge/清理任务长时间停滞 |
| RIGHT JOIN 子查询线程估算 | 查询计划/流水线(src/Processors/QueryPlan) | 右连接查询的线程分配更准确 |
| pointInPolygon 关闭校验崩溃 | 几何函数(pointInPolygon.cpp) | 封堵畸形多边形输入下的崩溃路径 |
如果你正在使用 21.6 LTS 分支,且业务涉及 TTL 列变更、后台任务压力较大、右连接分析或地理围栏类查询(pointInPolygon),建议优先升级到该版本或更新的 21.6.x 补丁。升级前请在测试环境回归上述场景,特别是validate_polygons = 0的存量查询。
【免费下载链接】ClickHouseClickHouse® is a real-time analytics database management system项目地址: https://gitcode.com/GitHub_Trending/cli/ClickHouse
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考