跳转到主要内容
本文档介绍了 ClickHouse 的回移策略,以及用于实施该策略的自动化系统。

发布模型

ClickHouse 版本遵循 YY.M.patch.build-type 方案,其中 YY 表示两位年份,M 表示发布月份 (不补前导零) ,patch 是该分支内的补丁号,build 是单调递增的构建编号,type 则为 stablelts 示例:25.3.8.23-lts — 2025 年 3 月的 LTS 版本,补丁 8,构建 23。 发布分为两条路线:
  • 稳定版本大致每月发布一次。最近的三个稳定版本会接收补丁,因此每个版本大约有三个月的活跃支持期。
  • **LTS (长期支持) **版本每年在 3 月和 8 月发布。系统会同时支持两个 LTS 版本,且每个版本的支持期至少为 12 个月。
建议在生产环境中运行工作负载的用户使用最新的稳定版本或 LTS 版本,并及时升级到新的补丁版本,因为补丁版本不会引入破坏性变更。

回移策略

并非所有更改都会回移。回移的目标是保持发布分支稳定,因此回移范围被有意控制在较小范围内:
  • 安全修复 — 始终回移。
  • 严重缺陷修复 (异常 (逻辑错误) 、数据丢失、错误结果、RBAC 问题) — 根据通用回移规则自动纳入回移范围;通过 pr-critical-bugfix 标签识别,该标签会自动添加 pr-must-backport
  • 稳定性修复和回归修复 — 当变更本身的风险低于保留该缺陷不修复的风险时,会进行回移;通过由维护者手动添加的 pr-must-backport 识别。
  • 有可用变通方案的轻微缺陷修复 — 通常不回移,以避免影响发布分支的稳定性。
  • 新功能、改进、性能优化工作 — 不回移。
标签 pr-must-backport 是维护者用于将 PR 标记为需要回移的手动覆盖机制。标签 pr-critical-bugfix 会触发 CI 钩子自动添加 pr-must-backport (参见 pr_labels_and_category.py) 。 冲突处理。 当自动回移无法解决合并冲突时,仍必须创建一个 cherry-pick PR,并将其分配给原始 PR 的作者、合并者以及现有受分配人,以便由人工解决冲突并完成回移。

回移工具

上述回移策略由 tests/ci/cherry_pick.py 中的自动化工具实现。该工具以 GitHub Actions 工作流的形式运行在 ClickHouse 基础设施上,并满足全部要求:发现活跃的发布分支、筛选符合回移条件的拉取请求、执行两阶段的 cherry-pick 和回移流程、处理冲突、执行延迟策略,以及保持标签同步。 长期目标是将这套实现提炼为一个独立的开源 Python 工具,供其他项目采用。目标设计如下:
  • 可配置 — 所有策略参数 (符合条件的标签、延迟窗口、过旧拉取请求阈值、滚动发布期间的行为等) 都在配置文件中定义,使该工具无需修改代码即可适配任何项目的回移需求。
  • 可分发 — 打包为可从 PyPI 安装的自包含 Python wheel,不依赖 ClickHouse 的 CI 基础设施。
  • 可编程 — 为拉取请求、标签和发布分支提供清晰的对象模型,使用户能够基于核心引擎编写自定义工作流脚本。

测试

该独立工具计划包含一个专用测试套件和一套轻量级测试基础设施。该基础设施将能够创建临时 GitHub 仓库 (或本地等效环境) ,并预置以下内容:
  • 一组可配置的分支,用于表示各条发布线,
  • 带有各种回移标签组合的拉取请求,
  • 带有 release 标签并指向发布分支的发布 PR。
这样一来,测试就可以在不影响生产状态的情况下,针对一个真实但可丢弃的仓库,覆盖完整的自动化流程——标签检测、cherry-pick 分支创建、冲突处理、回移 PR 创建、指派逻辑、跳过 rolling-out 状态以及延迟策略。相同的基础设施还可在策略变更部署前复用于回归测试。

活跃的发布分支

活跃的发布分支,是指其对应的发布 PR (带有 release 标签) 在 GitHub 上仍处于打开状态的分支。回移自动化会在每次运行时动态发现这些分支,因此当切出新版本或旧版本到达生命周期终点时,无需修改配置。 在新版本部署期间,发布分支可能处于 rolling-out 状态 (即其发布 PR 带有 rolling-out 标签) 。为避免增加滚动发布的复杂性,处于 rolling-out 状态的分支会暂停常规回移。特定版本标签 (例如 v25.3-must-backport) 会覆盖这一行为,即使在滚动发布期间也会强制执行回移。

实现

概述

回移自动化以 CherryPick GitHub Actions 工作流 (.github/workflows/cherry_pick.yml) 的形式每小时运行一次,具体实现在 tests/ci/cherry_pick.py 中。它通过 GitHub API 运行,并在自托管的 style-checker-aarch64 runner 上执行本地 git 操作。 对于每一对 (原始 PR、发布分支) ,该流程分为两个阶段:
  1. 会创建一个 cherry-pick PR,用于将冲突解决与实际合并目标分离。如果没有冲突,则会自动合并。
  2. 会针对实际的发布分支创建一个 回移 PR,并将 cherry-pick 得到的更改压缩为单个提交。

标签

原始 PR 上的标签决定是否进行回移,以及回移到哪些位置。
标签作用
pr-must-backport回移到所有活跃的发布分支 (跳过标记为 rolling-out 的分支)
pr-must-backport-force回移到所有活跃的发布分支,忽略 rolling-out 限制
pr-critical-bugfix自动触发 pr-must-backport (通过 pr_labels_and_category.py 中的 AUTO_BACKPORT)
v{VER}-must-backport (例如 v25.3-must-backport)仅回移到该特定发布分支;会覆盖该分支的 rolling-out 跳过规则
pr-backports-created当所有必需的回移 PR 都已创建时由机器人设置;如果某个 cherry-pick PR 被重新打开,则会被清除
pr-cherrypick用于标记由机器人创建的 cherry-pick PR
pr-backport用于标记由机器人创建的回移 PR
do not test用于标记 cherry-pick PR,以便 CI 不会在其上运行
rolling-out设置在 发布 PR 上,表示其分支当前正在滚动发布;常规回移会跳过该分支

分支和 PR 命名

对于每个原始 PR 编号 N 和发布分支 release/X.Y
  • Cherry-pick 分支:cherrypick/release/X.Y/N
  • backport 分支:backport/release/X.Y/N
  • Cherry-pick PR 标题:Cherry pick #N to release/X.Y: <original title>
  • Backport PR 标题:Backport #N to release/X.Y: <original title>

逐步操作流程

1. 发现活跃的发布分支

BackportPRs.receive_release_prs 会在 GitHub 上查询所有带有 release 标签的未关闭 PR。这些 PR 的 head ref 就是发布分支名 (例如 release/25.3) 。系统会据此生成一组兼容性标签:v25.3-must-backport 等。

2. 查找需要回移的 PR

BackportPRs.receive_prs_for_backport 使用 GitHub 搜索 API 查找满足以下条件的已合并 PR:
  • 带有至少一个回移标签 (pr-must-backportpr-must-backport-forcepr-critical-bugfix 或特定版本标签) ,且
  • 带有 pr-backports-created,且
  • 合并时间晚于任一发布分支上找到的最早提交日期,且
  • 在过去 90 天内有更新 (以提高搜索查询效率) 。

3. rolling-out 分支处理

当发布 PR 带有 rolling-out 标签时,通用的回移标签 (pr-must-backportpr-critical-bugfix) 会跳过该分支。机器人会关闭此前为该分支创建的所有 cherry-pick 或回移 PR,并附上说明性注释。特定版本的标签 (例如 v25.3-must-backport) 始终会覆盖这一行为。pr-must-backport-force 会绕过对所有分支的 rolling-out 检查。

4. Cherry-pick 阶段 (ReleaseBranch.create_cherrypick)

对于每一组尚未存在 cherry-pick PR 的 (原始 PR、发布分支) 组合:
  1. 检出发布分支,并基于它创建一个 backport 分支 (backport/release/X.Y/N) 。
  2. 对合并提交的第一个父提交执行 git merge -s ours,以创建一个不包含任何内容变更的合成合并基线。
  3. 强制创建一个直接指向原始 PR 的合并提交的 cherry-pick 分支 (cherrypick/release/X.Y/N) 。
  4. 尝试使用 git merge --no-commit --no-ff 将 cherry-pick 分支合并到 backport 分支:
    • 如果已是最新状态,则说明该变更已存在于发布分支中——标记为完成并跳过。
    • 否则 (无论是否有冲突) ,重置并推送这两个分支。
  5. 创建一个 cherry-pick PR,将 cherrypick/release/X.Y/N 合并到 backport/release/X.Y/N,并添加 pr-cherrypickdo not test 标签。
  6. 如果适用,则从原始 PR 继承 pr-bugfixpr-critical-bugfix
  7. 此时不会设置受分配人;只有在检测到冲突时才会添加。

5. 无冲突的 cherry-pick PR 自动合并

如果 cherry-pick PR 可合并 (无冲突) ,机器人会通过 GitHub API 自动合并它,并立即进入回移阶段。

6. 回移阶段 (ReleaseBranch.create_backport)

在 cherry-pick PR 合并后:
  1. 检出并拉取 backport 分支。
  2. 找到 release branch 与 backport 分支之间的 merge-base。
  3. 执行 git reset --soft 回退到该 merge-base,将所有 cherry-pick 过的提交 squash 为一个提交。
  4. 使用回移 PR 的标题作为提交消息进行提交。
  5. 强制推送 backport 分支,并创建一个以实际 release branch 为目标分支的回移 PR。
  6. 为该 PR 添加 pr-backport label (如适用,还需添加 pr-bugfix / pr-critical-bugfix) 。
  7. 将该 PR 分配给原始 PR 的作者、合并者以及现有受分配人 (不包括机器人账户) 。

7. 完成

当某个原始 PR 对应的所有发布分支都已完成回移后,机器人会在该原始 PR 上添加 pr-backports-created

8. 预检查

在开始处理某个 PR 之前,ReleaseBranch.pre_check 会运行 git merge-base --is-ancestor,以确认该合并提交尚未包含在发布分支的历史中。如果已经包含,则该 PR 会被视为已回移并跳过。

长时间未处理的 Cherry-pick PR 处理

CherryPickPRs 类会在每小时执行开始时运行,并处理以下两种情况:
  • 孤立的 cherry-pick PR:如果某个 cherry-pick PR 所在的发布分支不再有处于打开状态的发布 PR (即该发布已关闭) ,则该 cherry-pick PR 会被自动关闭。
  • 重新打开的 cherry-pick PR:如果某个原始 PR 已带有 pr-backports-created,但对应的 cherry-pick PR 仍处于打开状态,则会从原始 PR 中移除 pr-backports-created 标签,以便重新处理。
对于等待人工解决冲突的 cherry-pick PR:
  • 3 天未更新后,机器人会发布一条 ping 评论并提及受分配人。
  • 7 天未更新后,机器人会发布一条关闭评论,并关闭该 PR。

冲突处理

当 cherry-pick 发生冲突时,相应的 cherry-pick PR 会保持打开状态,等待人工处理。机器人会将其分配给原始 PR 的作者、合并者和受分配人。冲突解决并且 cherry-pick PR 合并后,机器人会在下一次每小时运行时创建回移 PR。 如果要完全放弃某次回移,请关闭 cherry-pick PR。机器人会将其视为有意跳过。 要从头重新创建一个损坏的 cherry-pick PR:
  1. 从 cherry-pick PR 中移除 pr-cherrypick 标签。
  2. 删除 cherrypick/... 分支。
  3. 如果存在,请从原始 PR 中移除 pr-backports-created

Backport PR 的 CI

Backport PR 面向发布分支,因此使用专门的 CI 工作流 (BackportPR,定义在 ci/workflows/backport_branches.py 中) ,而不是标准的拉取请求工作流。该工作流会运行一组具有代表性的 CI 子集:ASan/UBSan 和 TSan 构建、发布构建、macOS 构建、在 ASan 下运行的功能测试、在 TSan 下运行的压力测试,以及集成测试。它还会验证 backport 分支是否包含 1 到 50 个提交,且至少有一个被更改的文件 (由 check_backport_branch.py 强制检查) 。

身份验证

该工作流使用 SSH 密钥 (ROBOT_CLICKHOUSE_SSH_KEY) 执行 git push 操作。GitHub API 调用通过 get_best_robot_token 进行身份验证;该函数会从存储在 SSM (/github-tokens) 中的令牌池中,选择剩余额度最多的令牌。ROBOT_CLICKHOUSE_COMMIT_TOKEN 由 Actions 工作流中的 checkout 步骤使用,不用于 API 调用。分配负责人时,会排除机器人账户 (robot-clickhouseclickhouse-gh) 。

GitHub API 缓存

GitHubCache (来自 cache_utils.py) 会将 PyGithub 对象缓存持久化到 S3,以减少每小时运行时的 API 调用次数。缓存会在每次运行开始时下载,并在结束时上传。

错误处理

处理各个 PR 时产生的错误会被捕获并记录,但不会导致本次运行停止。所有 PR 处理完毕后,如果期间出现过任何错误,则会抛出 BackportException。在 CI 中,这会通过 CIBuddy 向团队聊天发送通知。
最后修改于 2026年6月10日