发布模型
YY.M.patch.build-type 方案,其中 YY 表示两位年份,M 表示发布月份 (不补前导零) ,patch 是该分支内的补丁号,build 是单调递增的构建编号,type 则为 stable 或 lts。
示例:25.3.8.23-lts — 2025 年 3 月的 LTS 版本,补丁 8,构建 23。
发布分为两条路线:
- 稳定版本大致每月发布一次。最近的三个稳定版本会接收补丁,因此每个版本大约有三个月的活跃支持期。
- **LTS (长期支持) **版本每年在 3 月和 8 月发布。系统会同时支持两个 LTS 版本,且每个版本的支持期至少为 12 个月。
回移策略
- 安全修复 — 始终回移。
- 严重缺陷修复 (异常 (逻辑错误) 、数据丢失、错误结果、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 基础设施。
- 可编程 — 为拉取请求、标签和发布分支提供清晰的对象模型,使用户能够基于核心引擎编写自定义工作流脚本。
测试
- 一组可配置的分支,用于表示各条发布线,
- 带有各种回移标签组合的拉取请求,
- 带有
release标签并指向发布分支的发布 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、发布分支) ,该流程分为两个阶段:
- 会创建一个 cherry-pick PR,用于将冲突解决与实际合并目标分离。如果没有冲突,则会自动合并。
- 会针对实际的发布分支创建一个 回移 PR,并将 cherry-pick 得到的更改压缩为单个提交。
标签
| 标签 | 作用 |
|---|---|
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 命名
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-backport、pr-must-backport-force、pr-critical-bugfix或特定版本标签) ,且 - 不带有
pr-backports-created,且 - 合并时间晚于任一发布分支上找到的最早提交日期,且
- 在过去 90 天内有更新 (以提高搜索查询效率) 。
3. rolling-out 分支处理
rolling-out 标签时,通用的回移标签 (pr-must-backport、pr-critical-bugfix) 会跳过该分支。机器人会关闭此前为该分支创建的所有 cherry-pick 或回移 PR,并附上说明性注释。特定版本的标签 (例如 v25.3-must-backport) 始终会覆盖这一行为。pr-must-backport-force 会绕过对所有分支的 rolling-out 检查。
4. Cherry-pick 阶段 (ReleaseBranch.create_cherrypick)
- 检出发布分支,并基于它创建一个 backport 分支 (
backport/release/X.Y/N) 。 - 对合并提交的第一个父提交执行
git merge -s ours,以创建一个不包含任何内容变更的合成合并基线。 - 强制创建一个直接指向原始 PR 的合并提交的 cherry-pick 分支 (
cherrypick/release/X.Y/N) 。 - 尝试使用
git merge --no-commit --no-ff将 cherry-pick 分支合并到 backport 分支:- 如果已是最新状态,则说明该变更已存在于发布分支中——标记为完成并跳过。
- 否则 (无论是否有冲突) ,重置并推送这两个分支。
- 创建一个 cherry-pick PR,将
cherrypick/release/X.Y/N合并到backport/release/X.Y/N,并添加pr-cherrypick和do not test标签。 - 如果适用,则从原始 PR 继承
pr-bugfix或pr-critical-bugfix。 - 此时不会设置受分配人;只有在检测到冲突时才会添加。
5. 无冲突的 cherry-pick PR 自动合并
6. 回移阶段 (ReleaseBranch.create_backport)
- 检出并拉取 backport 分支。
- 找到 release branch 与 backport 分支之间的 merge-base。
- 执行
git reset --soft回退到该 merge-base,将所有 cherry-pick 过的提交 squash 为一个提交。 - 使用回移 PR 的标题作为提交消息进行提交。
- 强制推送 backport 分支,并创建一个以实际 release branch 为目标分支的回移 PR。
- 为该 PR 添加
pr-backportlabel (如适用,还需添加pr-bugfix/pr-critical-bugfix) 。 - 将该 PR 分配给原始 PR 的作者、合并者以及现有受分配人 (不包括机器人账户) 。
7. 完成
pr-backports-created。
8. 预检查
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标签,以便重新处理。
- 3 天未更新后,机器人会发布一条 ping 评论并提及受分配人。
- 7 天未更新后,机器人会发布一条关闭评论,并关闭该 PR。
冲突处理
- 从 cherry-pick PR 中移除
pr-cherrypick标签。 - 删除
cherrypick/...分支。 - 如果存在,请从原始 PR 中移除
pr-backports-created。
Backport PR 的 CI
BackportPR,定义在 ci/workflows/backport_branches.py 中) ,而不是标准的拉取请求工作流。该工作流会运行一组具有代表性的 CI 子集:ASan/UBSan 和 TSan 构建、发布构建、macOS 构建、在 ASan 下运行的功能测试、在 TSan 下运行的压力测试,以及集成测试。它还会验证 backport 分支是否包含 1 到 50 个提交,且至少有一个被更改的文件 (由 check_backport_branch.py 强制检查) 。
身份验证
ROBOT_CLICKHOUSE_SSH_KEY) 执行 git push 操作。GitHub API 调用通过 get_best_robot_token 进行身份验证;该函数会从存储在 SSM (/github-tokens) 中的令牌池中,选择剩余额度最多的令牌。ROBOT_CLICKHOUSE_COMMIT_TOKEN 由 Actions 工作流中的 checkout 步骤使用,不用于 API 调用。分配负责人时,会排除机器人账户 (robot-clickhouse、clickhouse-gh) 。
GitHub API 缓存
GitHubCache (来自 cache_utils.py) 会将 PyGithub 对象缓存持久化到 S3,以减少每小时运行时的 API 调用次数。缓存会在每次运行开始时下载,并在结束时上传。
错误处理
BackportException。在 CI 中,这会通过 CIBuddy 向团队聊天发送通知。