ClickHouse 21.3.9.83-lts 发布解析:TDigest 分位数精度修复与 8 项关键缺陷回补
2026/9/12 1:58:35 网站建设 项目流程

ClickHouse 21.3.9.83-lts 发布解析:TDigest 分位数精度修复与 8 项关键缺陷回补

【免费下载链接】ClickHouseClickHouse® is a real-time analytics database management system项目地址: https://gitcode.com/GitHub_Trending/cli/ClickHouse

导读

本文以 ClickHouse 官方变更日志 docs/changelogs/archive/v21.3.9.83-lts.md 为骨架,系统梳理 v21.3.9.83-lts 相对 v21.3.8.76-lts 的 1 项功能改进与 8 项缺陷修复。这是一次典型的 LTS 维护分支回补(backport)版本:所有变更均来自主干 PR 的回传,涉及 t-digest 分位数聚合、ReplicatedMergeTree 后台清理、分区裁剪、PREWHERE 优化、垂直合并等核心路径。读完本文,你将清楚每一项修复的触发场景、对应源码位置、涉及的表引擎与聚合函数,以及如何在自己的查询与运维实践中规避同类问题。

版本背景:LTS 分支的维护节奏

21.3 是 ClickHouse 的长期支持(LTS)版本线,其维护分支会持续接收来自更新版本主干的高价值修复,并以“FIXME 相对上一补丁版本”的格式记录差异。本版本对标的基线与回补关系为:

  • 比较基线:v21.3.8.76-lts
  • 发布版本:v21.3.9.83-lts
  • 变更构成:1 项 Improvement + 8 项 Bug Fix,全部标注为Backported in,即由主干 PR 回传至 LTS 分支

其中每一项变更同时关联了上游 issue/PR 编号,例如 #23680、#23579 等。这种“issue → PR → backport PR”的三级追踪体系是 ClickHouse 变更日志的标准结构,便于用户在历史版本间核对修复来源。

Improvement:修复 quantileTDigest 的单点质心处理

变更内容

本次唯一的 Improvement 聚焦 t-digest 算法实现:

修复了quantile(s)TDigest。依据 tdunning/t-digest 3.2+ 规范增加了对单点质心(singleton centroid)的特殊处理;同时修复了早期算法实现中质心过度压缩(over-compression)导致的精度问题。

t-digest 是一种面向大规模流数据的近似分位数算法,其核心数据结构由若干“质心”(centroid)组成,每个质心保存均值(mean)与权重(count)。实现位于 src/AggregateFunctions/QuantileTDigest.h,聚合函数外壳在 src/AggregateFunctions/AggregateFunctionQuantileTDigest.cpp。

源码层面的印证

在 QuantileTDigest.h 的getImpl(以及多分位数版本getManyImpl)中,可以清晰看到“特殊处理单点质心”的逻辑:

/// Special handling of singletons. Float64 left = prev_x + 0.5 * (prev_count == 1); Float64 right = current_x - 0.5 * (c.count == 1);

当某个质心权重恰为 1(即该质心只代表一个数据点)时,查询会在质心区间边界处直接返回质心的 mean,而不是对区间做线性插值——这正是 tdunning/t-digest 3.2+ 规范的修正点。

关于“过度压缩”问题,源码注释中也给出了设计约束(QuantileTDigest.h):

/// N.B. We cannot merge all the same values into single centroids because this will lead to /// unbalanced compression and wrong results. /// For more information see: https://arxiv.org/abs/1902.04023

即早期实现对相同取值过度合并为单一质心会破坏压缩的均衡性、产生错误的分位数结果。修复后,压缩流程compress()会依据4·q·(1-q)·δ·N的质心容量上界决定是否合并相邻质心,并将未合并计数unmerged复位;compressBrute()作为兜底保证质心数不超过max_centroids

对使用者的意义

quantileTDigest(level)(expr)与批量版本quantilesTDigest(level1, level2, ...)(expr)(两者均注册于 AggregateFunctionQuantileTDigest.cpp)适合在数据量大、需要近似分位数的场景下使用,官方文档声明其内存消耗为log(n)。修复后:

  • 单点质心(如样本中出现大量唯一值、且 level 落在边缘分位附近)时结果更准确;
  • 早期版本“把所有相同值压进一个质心”导致的分布偏差被消除。

推荐level取值在[0.01, 0.99]区间内;level=0.5时即计算近似中位数。注意结果依赖数据到达顺序、具有不确定性,且性能低于精确版quantile

Bug Fix 一:修复后台清理旧 block 的极罕见竞态

对应 PR #23301:修复了后台清理旧数据块时极罕见的竞态条件(race condition)。在 ReplicatedMergeTree 家族中,去重(deduplication)依赖写入 block 的校验和与去重窗口;该竞态可能导致:若某 block 恰好落在去重窗口(dedup window)末端附近,它可能未被去重而重复写入。

从修复意图看,这正是表引擎数据一致性保障的一部分:写入去重(replicated_deduplication_window相关逻辑)与后台分区清理必须互斥协同。对于重度依赖复制表幂等写入的用户,升级该补丁可消除“窗口边界上偶发重复数据”的隐患。

Bug Fix 二:分区裁剪不再放宽 NOT 条件

对应 PR #23310,修复了 #23305 与 #21539 两个 issue:

分区裁剪(partition pruning)时不再放宽 NOT 条件(Don't relax NOT conditions during partition pruning)。

分区裁剪是 MergeTree 家族查询优化的第一道过滤:引擎会依据分区键表达式判断哪些分区可以被跳过。此前实现对含NOT的条件做了过度乐观的剪枝推断,导致被裁剪的分区中可能包含满足条件的数据(即裁剪结果不精确,可能漏数据)。修复后,NOT条件在分区裁剪阶段被保守处理,宁可多扫分区、也绝不因裁剪丢数据。相关查询执行路径见 src/Storages/MergeTree/MergeTreeDataSelectExecutor.cpp。

Bug Fix 三:修复 dict join 在 join_algorithm='auto' 下的缺陷

对应 PR #23312,关闭 issue #23002:

修复了join_algorithm = 'auto'时 dict join 的 bug。

join_algorithm='auto'是 Join 引擎的智能路由模式:执行器会根据右表数据量、键类型等特征,在hashpartial_mergedirectfull_sorting_merge等算法间自动选择;dict join 则利用字典(Dictionary)加速连接。在 auto 模式下算法切换边界存在判断缺陷,可能导致连接结果错误。修复后,auto 模式下的字典连接路径与显式join_algorithm='direct'(配合字典)行为一致。

Bug Fix 四:恢复 21.1 之前“将更多条件移入 PREWHERE”的优化能力

对应 PR #23397:

允许像 21.1 版本之前那样将更多条件移动到PREWHERE。此前可移动条件数量不足会导致查询性能变差。

PREWHERE 优化是 ClickHouse MergeTree 读取路径的核心加速手段:将过滤条件尽可能下推到列读取阶段,先只读过滤列、过滤后再读取其余列,从而显著减少 IO。负责该决策的组件是 src/Storages/MergeTree/MergeTreeWhereOptimizer.cpp,其主循环(L682-L714)逐条评估where_conditions中的候选条件:

  • 使用“移动条件涉及列大小之和 / 查询列总大小”的 10% 启发式阈值(无法获知列大小时退化为 25% 列数比阈值)决定何时停止迁移;
  • 受设置项move_all_conditions_to_prewhere(全部迁移)与allow_reorder_prewhere_conditions(是否保持条件原始顺序)控制,见 L690-L709。

21.1 引入的回归使可迁移条件数被低估,导致本应进入 PREWHERE 的过滤条件滞留 WHERE,读取了不必要的列。本修复恢复了此前的迁移强度,对“宽表 + 强过滤”类查询有直接的性能收益。

Bug Fix 五:修复多磁盘 ReplicatedMergeTree 建表失败残留Cannot unlink file

对应 PR #23433,关闭 issue #21755:

修复了使用多磁盘(multidisk)配置创建 ReplicatedMergeTree 表失败时抛出Cannot unlink file错误的问题。

多磁盘存储(storage_policy下的volumes/disks配置,例如move_factor与 TTL 冷热分层)是 ClickHouse 容量管理的基础能力。此前在“创建复制表失败后的清理路径”上,未能正确处理跨磁盘分发的临时文件,导致回滚时报Cannot unlink file,掩盖了真正的失败原因。修复后,失败清理逻辑按磁盘正确解除文件引用,报错信息回归本源。涉及实现位于 src/Storages/MergeTree 目录下的建表与磁盘管理路径。

Bug Fix 六:修复 ReplacingMergeTree 垂直合并的边界缺陷

对应 PR #23459:

修复ReplacingMergeTree垂直合并(vertical merge)的边界情况。罕见情况下合并会以Incomplete granules are not allowed while blocks are granules size异常失败。

垂直合并(vertical merge)按列而非按分区整体合并,能显著减少合并 IO,但对 granule(粒度,默认 8192 行)边界的一致性要求极高。修复前,ReplacingMergeTree在特定行数分布下,垂直合并会构造出“非完整 granule”的中间 block,触发上述异常并中断合并。修复后,合并路径保证每个参与垂直合并的 block 都严格对齐 granule 边界。

Bug Fix 七:修复 restart / stop 命令挂起

对应 PR #23552,关闭 issue #20214:

修复restart/stop命令挂起的问题。

该问题影响clickhouse-server的守护进程管理(如 systemd 下的 stop/restart,或clickhouse stop脚本)。当有长期运行的查询或未完成的异步任务阻塞退出流程时,旧版本可能无限等待导致进程无法停止。修复调整了退出路径中的等待与信号处理逻辑,使 stop/restart 在存在活跃工作负载时也能可靠完成。

Bug Fix 八:mannWhitneyUTest 对两组样本完全相同时抛出异常

对应 PR #23654,关闭 issue #23646:

当聚合函数mannWhitneyUTest的两个样本取值完全相同时,新增异常抛出(而不是返回无意义的结果)。

mannWhitneyUTest是 ClickHouse 内置的 Mann-Whitney U 秩和检验聚合函数,实现位于 src/AggregateFunctions/AggregateFunctionMannWhitney.cpp。其统计逻辑(L48-L107)中,检验统计量依赖秩(rank)与结修正(tie correction):

/// Handle the case when tie_correction is close to zero (all values are identical) Float64 sd = 0.0; if (std::abs(tie_correction) > std::numeric_limits<Float64>::epsilon()) sd = std::sqrt(tie_correction * n1 * n2 * (n1 + n2 + 1) / 12.0);

当两组样本取值完全一致时,所有秩相同、结修正趋近 0,标准差sd退化为 0,此时 U 统计量/z 值无法定义(z 计算会除以 0),此前会返回虚假的 p 值。修复后,insertResultInto(L249-L265)检测到该退化情形直接抛出异常,避免误导性的统计结论。

该函数语法为mannWhitneyUTest[(alternative[, continuity_correction])](sample_data, sample_index),支持two-sided(默认)/less/greater三种备择假设,返回(u_statistic, p_value)元组(L297-L329)。官方示例:

CREATE TABLE mww_ttest (sample_data Float64, sample_index UInt8) ENGINE = Memory; INSERT INTO mww_ttest VALUES (10, 0), (11, 0), (12, 0), (1, 1), (2, 1), (3, 1); SELECT mannWhitneyUTest('greater')(sample_data, sample_index) FROM mww_ttest; -- ┌─mannWhitneyUTest('greater')(sample_data, sample_index)─┐ -- │ (9,0.04042779918502615) │ -- └────────────────────────────────────────────────────────┘

该函数不假设数据服从正态分布,属于非参数检验,适用于 A/B 实验等分布未知场景;本次修复后,对“两组样本完全相同”这种统计退化输入会显式报错而非静默给出伪结果。

变更总览与升级建议

类别变更主题关联源码/模块
Improvementquantile(s)TDigest 单点质心处理与压缩精度修复src/AggregateFunctions/QuantileTDigest.h
Bug Fix后台清理旧 block 的罕见竞态(影响去重窗口)ReplicatedMergeTree 后台清理/去重逻辑
Bug Fix分区裁剪不再放宽 NOT 条件src/Storages/MergeTree/MergeTreeDataSelectExecutor.cpp
Bug Fixdict join 在 join_algorithm='auto' 下的缺陷Join 引擎字典连接路径
Bug Fix恢复 21.1 前的 PREWHERE 条件迁移强度src/Storages/MergeTree/MergeTreeWhereOptimizer.cpp
Bug Fix多磁盘 ReplicatedMergeTree 建表失败残留Cannot unlink filesrc/Storages/MergeTree 建表清理路径
Bug FixReplacingMergeTree 垂直合并 granule 边界异常MergeTree 垂直合并路径
Bug Fixrestart / stop 命令挂起服务退出/信号处理路径
Bug FixmannWhitneyUTest 对完全相同的两组样本抛异常src/AggregateFunctions/AggregateFunctionMannWhitney.cpp

升级建议按影响面取舍:所有使用 ReplicatedMergeTree 且依赖写入去重的生产集群建议优先升级(修复去重窗口竞态与多磁盘建表清理);重度使用 PREWHERE 优化或quantileTDigest的查询场景将直接获得精度与性能收益;使用mannWhitneyUTest做统计检验的分析任务需注意新版本对退化输入的显式报错行为。完整变更记录可查阅 docs/changelogs/archive/v21.3.9.83-lts.md,各修复对应的 upstream issue 编号均可按 changelog 内链接回溯。

【免费下载链接】ClickHouseClickHouse® is a real-time analytics database management system项目地址: https://gitcode.com/GitHub_Trending/cli/ClickHouse

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

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

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

立即咨询