ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

ClickHouse v25.6.12.10-stable 发布解读:S3 多段上传竞态修复与 PREWHERE、Backup 稳定性增强

ClickHouse v25.6.12.10-stable 发布解读:S3 多段上传竞态修复与 PREWHERE、Backup 稳定性增强 ClickHouse v25.6.12.10-stable 发布解读S3 多段上传竞态修复与 PREWHERE、Backup 稳定性增强【免费下载链接】ClickHouseClickHouse® is a real-time analytics database management system项目地址: https://gitcode.com/GitHub_Trending/cli/ClickHouse导读本文基于 docs/changelogs/v25.6.12.10-stable.md 官方 Changelog对 ClickHouse v25.6.12.10-stable 相比 v25.6.11.18-stable 的 1 项改进与 3 项用户可见 Bug 修复进行逐条深度解析。通过对照src/IO、src/Storages、src/Backups、src/DataTypes等目录下的真实实现与单元测试读者可以理解每个修复背后的底层原理、触发场景以及如何在升级后验证修复是否生效。版本概览25.6 系列维护分支的一次常规补丁ClickHouse 的版本号遵循YY.M.patch-stable的格式25.6表示 2025 年 6 月发布的特性版本而v25.6.12.10-stable则是该系列的第 12 个维护迭代。本次发布基于 commitb08b830e00f对比基线为v25.6.11.18-stable726d033db70是典型的小步快跑式稳定分支补丁。从 Changelog 的结构可以看出 ClickHouse 官方对稳定版本的两种分类Improvement改进优化用户体验、错误信息或内部处理逻辑不改变外部行为正确性Bug Fix (user-visible misbehavior in an official stable release)修复在官方稳定版中可被用户观察到的错误行为属于必须尽快 backport 的修复。本次全部修复都带有Backported in标记说明这些变更先在主干master分支被合并再被移植backport回 25.6 稳定分支。值得注意的是Changelog 标题中还保留了FIXME as compared to字样——这是 ClickHouse 自动生成 Changelog 的模板占位符用于在发布时人工补充对比说明属于正常现象而非内容缺失。下面按 Changelog 顺序逐条展开。Improvement修复 S3 多段上传中误导性的 specified upload does not exist 错误对应 PR 编号 #86725作者 Julia Kartseva经 #87033 回移植入 25.6 分支。问题场景多段上传Multipart Upload中的竞态条件当 ClickHouse 向 S3或兼容 S3 的对象存储写入较大的对象时会走多段上传流程CreateMultipartUpload创建上传任务并拿到upload id→ 按s3_min_upload_part_size逐个UploadPart→ 最后CompleteMultipartUpload提交完成。问题出在最后一步当多个请求例如写重试、连接超时后的请求重放、并发路径针对同一个对象重复发起CompleteMultipartUpload时可能出现竞态——第一个请求已经在服务端成功提交后续重放的请求再携带同一个upload id提交时S3 会返回NoSuchUpload错误The specified upload does not exist. The upload ID may be invalid, or the upload may have been aborted or completed.在修复前如果原始成功响应的异常信息在竞态中丢失用户就会只看到这条令人困惑的报错误以为上传真的失败了但实际上对象已经完整写入。源码级原理WriteBufferFromS3 的完成与重试逻辑核心实现在 src/IO/WriteBufferFromS3.cpp 的completeMultipartUpload方法中。该方法在提交完成后对结果做如下分类处理成功直接返回记录S3CompleteMultipartUpload指标疑似成功后重放当错误为PreconditionFailed412或NO_SUCH_UPLOAD且对象确实由当前这个 WriteBuffer 自己写入时isObjectWrittenByThisBuffer()校验写令牌代码认为这是一次先前已完成写入的请求重放打印Multipart upload has completed by an earlier attempt of this write并视为成功返回不再抛出误导性异常——这正是本改进的核心逻辑瞬时错误通过isTransientCompleteMultipartUploadError判断后重试重试上限取max_unexpected_write_error_retries至少 1 次其他错误直接抛出S3Exception。也就是说本次改进并不是简单地吞掉错误而是引入了可证明的自有对象 已完成的上传 ID→ 判定为重放并安全返回成功的判据既消除了误导性报错又保留了真正失败场景下的抛错行为。测试佐证NO_SUCH_UPLOAD 注入测试在 src/IO/tests/gtest_writebuffer_s3.cpp 中测试框架定义了一个CompleteMPUNoSuchUploadInjection注入模型它在每次CompleteMultipartUpload时精确返回 S3 标准错误NoSuchUpload及其完整消息L815-L819用于模拟 upload id 已被服务端消费的情形。围绕该注入模型仓库提供了三个行为各异、语义互补的测试用例MultipartConditionalCompleteDoesNotMaskForeignObjectOnNoSuchUploadL1774-L1807对别人写入的对象使用条件写If-None-Match: *即使出现NO_SUCH_UPLOAD也必须抛错——因为键上存在对象不等于对象是我写的若误报成功会让条件创建悄悄丢失载荷测试同时断言对象内容与写令牌保持不变MultipartIfMatchCompleteDoesNotRecoverNoSuchUploadL1811-L1833If-Match条件写不铸造写令牌无法证明所有权因此存在性恢复逻辑在此路径不得生效MultipartUnconditionalCompleteStillRecoversNoSuchUploadL1837 起无条件完成继续保留对象存在即恢复成功的行为用于支撑copyS3File与磁盘写路径DiskS3。这三条用例恰好划定了修复的边界只有能证明对象是本次写入所产生时才允许把NO_SUCH_UPLOAD当作成功重放处理。这是本改进最值得借鉴的严谨之处。Bug Fix 1修复GROUP BY Nullable(JSON)对应 PR 编号 #86410作者 Pavel Kruglov经 #86877 回移植入 25.6 分支。JSON 数据类型的实现背景ClickHouse 的JSON类型是一种半结构化动态类型同一列可以容纳混合的嵌套结构schema 随数据动态演进内部按路径path自动维护子类型。其底层实现在 src/DataTypes/DataTypeObject.cpp根据SchemaFormat::JSON分支编译期可选择SimdJSONParser受allow_simdjson设置控制或RapidJSONParser、DummyJSONParser作为解析后端并通过SerializationJSON完成序列化。修复内容与触发场景Changelog 指出的问题是当GROUP BY的键列类型为Nullable(JSON)时分组计算会出现错误。从 src/AggregateFunctions/AggregateFunctionDistinctJSONPaths.cpp 的注释输出可以看到 JSON 类型的实际形态例如{a:[Int64],b:[Array(Nullable(Int64)),String],c.d.e:[Date],c.d.f:[Array(JSON(...))]}即一个 JSON 值内部本身就包含Nullable子类型和嵌套JSON数组。当外层的Nullable(JSON)作为分组键时旧代码在路径提取、空值NULL判定与类型比较链路上存在缺陷导致分组结果错误或异常。修复后GROUP BY Nullable(JSON)可以按照与其他类型一致的空值语义NULL 自成一组和 JSON 路径语义正确分组。这也与 src/DataTypes/validateGroupByKeyType.cpp 所承担的分组键类型合法性校验职责相互呼应——分组键的类型体系在 ClickHouse 中是经过专门校验与测试的任何新增的动态类型都需要同步完善这条链路。Bug Fix 2修复 Backup 数据库引擎在零大小 part 文件上抛异常对应 PR 编号 #86563作者 Max Justus Spransy经 #87086 回移植入 25.6 分支。修复场景Backup数据库引擎用于将备份数据以表的形式直接挂载查询。当备份内容中包含大小为零字节的 part 文件zero sized part files时旧版本会在备份/恢复流程中抛出异常导致整个备份任务失败。从 src/Backups/RestorerFromBackup.cpp 的恢复流程看恢复器会对任务队列tasks_to_run做空判断与批量调度而 src/Backups/BackupIO_File.cpp 则负责文件级备份 IO涉及对文件大小、切块等元数据的处理。零字节 part 在 MergeTree 体系中是合法的例如空分区、全 NULL 数据压缩后等因此备份、恢复路径必须对文件存在但大小为 0这一边界情况做显式处理而不是把它当作数据损坏。修复后包含零大小 part 文件的备份可以正常完成备份与恢复不再中断任务。关联的备份流程ClickHouse 的BACKUP/RESTORE语句在执行时会经历构建备份 → 写入备份存储 → 恢复器挂载/还原的完整链路。RestorerFromBackup中还包含storage_policy校验L707、分区过滤L857-L859 的多阶段操作处理以及表非空且未允许覆盖时拒绝恢复L1132等保护逻辑本次修复即是在这条链路上补齐了零大小文件的边界处理。Bug Fix 3修复 PREWHERE 被拆分为多步后输出类型转换错误对应 PR 编号 #87040作者 Antonio Andelic经 #87068 回移植入 25.6 分支。背景PREWHERE 多步读取Multiple Prewhere Steps优化这是本次 Changelog 中技术含量最高的一个修复。为了减少 MergeTree 读取的数据量ClickHouse 引入了enable_multiple_prewhere_read_steps设置当PREWHERE条件是由多个子条件用AND组合而成时可以把它们拆分成多个读取步骤每个步骤只读取自己需要的列并提前过滤行让后续步骤读取更少的数据。拆分逻辑的完整实现在 src/Storages/MergeTree/MergeTreeSplitPrewhereIntoReadSteps.cpp 的tryBuildPrewhereSteps函数中其算法流程在源码注释中有清晰描述列出PREWHERE条件根节点下所有由AND连接的子条件节点condition_root.function_base-getName() and才可拆分否则放弃拆分见 L201-L204为每个子条件收集其依赖的列集合fillRequiredColumns递归遍历 DAG 节点按依赖列数及列大小对条件排序注释说明当前由 Where Optimizer 提前排序将读取相同物理存储列的子条件归并到同一步骤——注意resolveStorageColumnName会把map.key_k0这类子列解析回物理存储列map保证同一列的子列条件进入同一步骤L33-L41通过addClonedDAGToDAGL83-L157按 DFS 把原ActionsDAG克隆进各步骤 DAG已由前置步骤计算过的节点会被标记为前置步骤的额外输出、并以输入节点形式引用到当前步骤L98-L113从而避免重复计算常量COLUMN节点与输入节点直接复用找出原 DAG 的全部输出将已在各步骤中算出的节点标记为对应步骤的输出剩余输出在最后一步补算。整个拆分的正确性要求是多步执行的结果必须与单步执行原 DAG 完全等价源码注释 L175 的NOTE明确强调这一点。本次修复的缺陷与修复点tryBuildPrewhereSteps会为每个步骤生成独立的ExpressionActions与 filter 条件步骤之间通过上一步输出 → 下一步输入传递中间结果。当被拆分的条件中包含类型转换表达式如CAST、子列访问、Nullable 包裹等时旧版本在跨步骤传递输出环节没有对输出的类型做正确转换导致后续步骤拿到的列类型与预期不符进而产生类型不匹配错误或错误的结果。修复后每个步骤 DAG 的输出都会按原 DAG 语义进行正确的类型转换对应 PR 标题中的 Correctly cast output。从 src/Storages/MergeTree/MergeTreeSelectProcessor.cpp 可以看到该机制的接入点getPrewhereActions会根据enable_multiple_prewhere_read_steps决定是否调用tryBuildPrewhereSteps一旦拆分成功日志会记录PREWHERE condition was split into N stepsL184并同时输出拆分后的dumpConditions()与原始dumpDAG()供排查比对。这一修复与 src/Storages/MergeTree/MergeTreeRangeReader.cpp、src/Storages/MergeTree/MergeTreeBlockReadUtils.cpp 等 MergeTree 读取链路组件共同作用保证了 PREWHERE 优化在列类型层面上的严谨性。升级与验证建议针对使用 25.6 系列版本的集群本次补丁可以从以下角度进行验证与回归S3 写入路径若曾观察到The specified upload does not exist这类错误信息并伴随数据实际已写入的现象升级后该场景应被识别为重放成功并输出Multipart upload has completed by an earlier attempt of this write的 INFO 日志。可通过system.query_log或服务端日志确认JSON 分组查询对包含GROUP BY nullable_json_col形态的查询做回归重点验证含 NULL 与含嵌套路径的值是否分组正确备份/恢复构造包含空分区或零字节 part 的 MergeTree 表执行BACKUP与RESTORE确认不再因zero sized part files抛异常PREWHERE 多步读取在enable_multiple_prewhere_read_steps1下运行带AND组合条件与类型转换如CAST、子列的查询对比拆分前后结果一致性并关注PREWHERE condition was split into N steps日志。所有上述修复均已进入 25.6 稳定分支可通过升级到v25.6.12.10-stable或更高 25.6 补丁版本获得。对于还在使用更早 25.6 补丁版本的用户建议在充分回归后尽快升级以消除这 4 处已确认的用户可见问题。【免费下载链接】ClickHouseClickHouse® is a real-time analytics database management system项目地址: https://gitcode.com/GitHub_Trending/cli/ClickHouse创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表