> ## Documentation Index
> Fetch the complete documentation index at: https://private-7c7dfe99-fix-nav-issues.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

> ClickHouseのバックポートポリシーとその自動化の概要

# バックポートシステム

このドキュメントでは、ClickHouseのバックポートポリシーと、それを実装する自動化システムについて説明します。

<div id="release-model">
  ## リリースモデル
</div>

ClickHouse のバージョンは `YY.M.patch.build-type` という形式に従います。ここで、`YY` は西暦の下 2 桁、`M` はリリース月 (先頭に 0 は付きません) 、`patch` はそのブランチ内のパッチ番号、`build` は単調増加するビルド番号、`type` は `stable` または `lts` のいずれかです。

例: `25.3.8.23-lts` — 2025 年 3 月の LTS、パッチ 8、ビルド 23。

リリーストラックは 2 つあります。

* **Stable** リリースは、おおむね毎月公開されます。直近 3 つの stable リリースに対してパッチが提供されるため、各リリースのアクティブサポート期間はおよそ 3 か月です。
* **LTS (Long-Term Support)** リリースは、毎年 3 月と 8 月に公開されます。2 つの LTS バージョンが同時にサポートされ、それぞれ少なくとも 12 か月間サポートされます。

本番ワークロードを実行しているユーザーには、最新の stable または LTS リリースのいずれかを使用し、新しいパッチバージョンへ速やかにアップグレードすることを推奨します。パッチリリースで互換性を損なう変更が導入されることはありません。

<div id="backport-policy">
  ## バックポートポリシー
</div>

すべての変更がバックポートされるわけではありません。リリースブランチの安定性を保つため、バックポートの対象範囲は意図的に限定されています。

* **セキュリティ修正** — 常にバックポートされます。
* **重大なバグ修正** (例外 (論理エラー) 、データ損失、誤った結果、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 の著者、マージした人、既存の 担当者 に割り当てる必要があります。これにより、人手で競合を解決し、バックポートを完了できます。

<div id="backport-tool">
  ## Backport Tool
</div>

上で説明したバックポートポリシーは、`tests/ci/cherry_pick.py` の自動化ツールで実装されています。このツールは ClickHouse のインフラストラクチャ上で GitHub Actions ワークフローとして実行され、アクティブなリリースブランチの検出、バックポート対象となるプルリクエストの選定、2 段階の cherry-pick とバックポート手順の実行、競合の管理、遅延ポリシーの適用、ラベルの同期維持など、必要な要件をすべてカバーしています。

長期的な目標は、この実装を、他のプロジェクトでも採用できるスタンドアロンのオープンソース Python ツールとして切り出すことです。想定している設計は次のとおりです。

* **設定可能** — すべてのポリシーパラメーター (対象ラベル、遅延期間、古いプルリクエストのしきい値、rolling-out 時の挙動など) を設定ファイルで表現し、コードを変更しなくても、あらゆるプロジェクトのバックポート要件に合わせてツールを適応できるようにします。
* **配布可能** — ClickHouse の CI インフラストラクチャに依存せず、PyPI からインストール可能な自己完結型の Python wheel としてパッケージ化します。
* **プログラム可能** — プルリクエスト、ラベル、リリースブランチを表す明確なオブジェクトモデルを公開し、ユーザーがコアエンジンの上に独自のワークフローをスクリプト化できるようにします。

<div id="testing">
  ### テスト
</div>

スタンドアロンツールの構成要素として計画されているものの 1 つに、軽量なテスト用インフラストラクチャを備えた専用のテストスイートがあります。このインフラストラクチャでは、以下があらかじめ用意された一時的な GitHub リポジトリ (またはローカルでの同等の環境) を起動できるようになります。

* リリースラインを表す、設定可能な一連のブランチ
* さまざまな組み合わせのバックポートラベルが付いたプルリクエスト
* リリースブランチを対象とし、`release` ラベルが付いたリリース PR

これによりテストでは、本番環境の状態に影響を与えることなく、実在するものの使い捨て可能なリポジトリを使って、ラベル検出、cherry-pick ブランチの作成、競合処理、バックポート PR の作成、担当者の割り当てロジック、`rolling-out` のスキップ、遅延ポリシーといった自動化ループ全体を検証できます。同じインフラストラクチャは、ポリシー変更をデプロイする前に回帰テストを行うためにも再利用できます。

<div id="active-release-branches">
  ## アクティブなリリースブランチ
</div>

アクティブなリリースブランチとは、対応するリリース PR (`release` ラベル付き) が GitHub 上でまだオープンになっているブランチを指します。バックポートの自動化では、実行のたびにこれらを動的に検出するため、新しいリリースが作成されたときや古いリリースがサポート終了を迎えたときでも、設定を変更する必要はありません。

新しいリリースのデプロイ中は、リリースブランチが **rolling-out** 状態 (リリース PR に `rolling-out` ラベルが付いている状態) になることがあります。ロールアウトを複雑にしないため、**rolling-out** 状態のブランチでは通常のバックポートは一時停止されます。バージョン固有のラベル (例: `v25.3-must-backport`) はこの動作を上書きし、ロールアウト中であってもバックポートを強制します。

<div id="implementation">
  ## 実装
</div>

<div id="overview">
  ### 概要
</div>

バックポート自動化は、`CherryPick` GitHub Actions ワークフロー (`.github/workflows/cherry_pick.yml`) として 1 時間ごとに実行され、`tests/ci/cherry_pick.py` に実装されています。GitHub API と、セルフホストの `style-checker-aarch64` ランナー上でのローカルな git 操作によって動作します。

このプロセスは、各 (元の PR、リリースブランチ) ペアごとに 2 段階で進みます。

1. 実際のマージ先から競合解決を切り離すために、**cherry-pick PR** が作成されます。競合がなければ、自動的にマージされます。
2. 実際のリリースブランチに対して **バックポート PR** が作成され、チェリーピックされた変更は 1 つのコミットにまとめられます。

<div id="labels">
  ### ラベル
</div>

元の 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** に設定されます。通常のバックポートではこのブランチはスキップされます          |

<div id="branch-and-pr-naming">
  ### ブランチと PR の命名
</div>

各 元の PR 番号 `N` とリリースブランチ `release/X.Y` について:

* cherry-pick ブランチ: `cherrypick/release/X.Y/N`
* バックポートブランチ: `backport/release/X.Y/N`
* cherry-pick PR タイトル: `Cherry pick #N to release/X.Y: <original title>`
* バックポート PR タイトル: `Backport #N to release/X.Y: <original title>`

<div id="step-by-step-process">
  ### 手順
</div>

<div id="discover-active-releases">
  #### 1. アクティブなリリースを特定する
</div>

`BackportPRs.receive_release_prs` は、`release` ラベルが付いたオープンな PR をすべて GitHub に対して照会します。これらの PR の head ref がリリースブランチ名です (例: `release/25.3`) 。そこから、`v25.3-must-backport` などの互換性ラベルのセットが導き出されます。

<div id="find-prs-to-backport">
  #### 2. バックポート対象のPRを見つける
</div>

`BackportPRs.receive_prs_for_backport` は GitHub Search API を使って、次の条件を満たすマージ済みPRを検索します。

* 少なくとも 1 つのバックポートラベル (`pr-must-backport`、`pr-must-backport-force`、`pr-critical-bugfix`、またはバージョン固有のラベル) が付いており、
* まだ `pr-backports-created` が付与されておらず、
* いずれかのリリースブランチで見つかった最も古いコミット日より後にマージされており、
* 過去 90 日以内に更新されている (検索クエリの効率を保つため) 。

<div id="rolling-out-branch-handling">
  #### 3. `rolling-out` ブランチの処理
</div>

リリース PR に `rolling-out` ラベルが付いている場合、一般的なバックポートラベル (`pr-must-backport`、`pr-critical-bugfix`) ではそのブランチはスキップされます。ボットは、そのブランチ向けに以前作成された cherry-pick または バックポート PR を、説明コメント付きでクローズします。バージョン固有のラベル (例: `v25.3-must-backport`) は、常にこの動作より優先されます。`pr-must-backport-force` は、すべてのブランチで `rolling-out` チェックを無視します。

<div id="cherry-pick-stage">
  #### 4. Cherry-pick 段階 (`ReleaseBranch.create_cherrypick`)
</div>

まだ cherry-pick PR が存在しない各 (元の PR、リリースブランチ) の組について:

1. リリースブランチ をチェックアウトし、そこから **バックポートブランチ** (`backport/release/X.Y/N`) を作成します。
2. マージコミットの第1親に対して `git merge -s ours` を実行し、内容変更を伴わない合成マージベースを作成します。
3. 元の PR のマージコミットを直接指す **cherry-pick branch** (`cherrypick/release/X.Y/N`) を強制的に作成します。
4. cherry-pick branch を backport branch に `git merge --no-commit --no-ff` でマージします。
   * すでに最新であれば、その変更は リリースブランチ にすでに含まれているため、完了としてマークしてスキップします。
   * それ以外の場合は (競合の有無にかかわらず) 、両方のブランチをリセットして push します。
5. `cherrypick/release/X.Y/N` から `backport/release/X.Y/N` への cherry-pick PR を作成し、`pr-cherrypick` と `do not test` の label を付与します。
6. 該当する場合は、元の PR から `pr-bugfix` または `pr-critical-bugfix` を引き継ぎます。
7. この時点では 担当者 は設定しません。担当者 が追加されるのは、競合が検出された場合のみです。

<div id="auto-merge-conflict-free-cherry-pick-prs">
  #### 5. 競合のない cherry-pick PR の自動マージ
</div>

cherry-pick PR をマージ可能な状態 (競合なし) であれば、ボットは GitHub API を通じて自動的にマージし、直ちに backport 段階に進みます。

<div id="backport-stage">
  #### 6. バックポート段階 (`ReleaseBranch.create_backport`)
</div>

cherry-pick PR がマージされたら、次の手順を実行します。

1. バックポートブランチをチェックアウトし、pull します。
2. リリースブランチとバックポートブランチの間の merge-base を見つけます。
3. merge-base に対して `git reset --soft` を実行し、cherry-pick したすべてのコミットを 1 つにまとめます。
4. バックポート PR のタイトルをメッセージとしてコミットします。
5. バックポートブランチを force-push し、実際のリリースブランチを対象とするバックポート PR を作成します。
6. PR に `pr-backport` のラベルを付けます (必要に応じて `pr-bugfix` / `pr-critical-bugfix` も付けます) 。
7. PR を、元の PR の著者、マージしたユーザー、および既存の担当者 (ロボットアカウントを除く) に割り当てます。

<div id="completion">
  #### 7. 完了
</div>

特定の元の PRに対応するすべてのリリースブランチへのバックポートが完了すると、ボットはその元の PRに`pr-backports-created`を追加します。

<div id="pre-check">
  #### 8. 事前チェック
</div>

PR の作業を開始する前に、`ReleaseBranch.pre_check` は `git merge-base --is-ancestor` を実行し、そのマージコミットがリリースブランチからすでにたどれる状態にないことを確認します。すでにたどれる状態にある場合、その PR はすでにバックポート済みと見なされ、スキップされます。

<div id="stale-cherry-pick-pr-handling">
  ### Stale Cherry-pick PR の処理
</div>

`CherryPickPRs` クラスは毎時実行の開始時に動作し、次の 2 つのケースを処理します。

* **孤立した cherry-pick PR**: cherry-pick PR のリリースブランチに対応するオープンな release PR が存在しなくなった場合 (つまりリリースがクローズされた場合) 、その cherry-pick PR は自動的にクローズされます。
* **再オープンされた cherry-pick PR**: original PR にすでに `pr-backports-created` が付いていても、それに対応する cherry-pick PR がまだオープンであれば、original PR から `pr-backports-created` ラベルが削除され、再処理できるようになります。

手動での競合解決を待っている cherry-pick PR については、次のように動作します。

* **3 日**間更新がない場合、bot は 担当者 にメンションする ping コメントを投稿します。
* **7 日**間更新がない場合、bot はクローズする旨のコメントを投稿し、その PR をクローズします。

<div id="conflict-resolution">
  ### 競合の解決
</div>

cherry-pick で競合が発生した場合、cherry-pick PR は人手で解決できるよう、開いたままになります。ボットはその PR を original PR の著者、マージした人、および 担当者 に割り当てます。競合が解決されて cherry-pick PR がマージされると、ボットは次の毎時実行時にバックポート PR を作成します。

バックポートを完全に破棄するには、cherry-pick PR をクローズしてください。ボットはそれを意図的にスキップされたものとして扱います。

問題のある cherry-pick PR を最初から作り直すには:

1. cherry-pick PR から `pr-cherrypick` ラベルを削除します。
2. `cherrypick/...` ブランチを削除します。
3. original PR に `pr-backports-created` があれば削除します。

<div id="ci-for-backport-prs">
  ### バックポート PR 向け CI
</div>

バックポート PR はリリースブランチを対象とするため、標準のプルリクエスト用ワークフローではなく、専用の CI ワークフロー (`ci/workflows/backport_branches.py` で定義されている `BackportPR`) を使用します。このワークフローでは、CI の主要な一部として、ASan/UBSan および TSan ビルド、リリースビルド、macOS ビルド、ASan での機能テスト、TSan でのストレステスト、結合テストを実行します。また、バックポートブランチにコミットが 1〜50 件あり、変更されたファイルが少なくとも 1 つ含まれていることを確認します (`check_backport_branch.py` によって強制されます) 。

<div id="authentication">
  ### 認証
</div>

このワークフローでは、git の push 操作に SSH 秘密鍵 (`ROBOT_CLICKHOUSE_SSH_KEY`) を使用します。GitHub API の呼び出しでは `get_best_robot_token` による認証を使用し、この関数は SSM (`/github-tokens`) に保存されたプールから、残り QUOTA が最も多いトークンを選択します。`ROBOT_CLICKHOUSE_COMMIT_TOKEN` は API 呼び出し用ではなく、Actions ワークフローの checkout step で使用されます。担当者を割り当てる際は、ロボットアカウント (`robot-clickhouse`、`clickhouse-gh`) は除外されます。

<div id="github-api-cache">
  ### GitHub API キャッシュ
</div>

`GitHubCache` (`cache_utils.py` 内) は PyGithub のオブジェクトcacheを S3 に永続化し、毎時の実行をまたぐ API 呼び出しを削減します。cache は各実行の開始時にダウンロードされ、終了時にアップロードされます。

<div id="error-handling">
  ### エラー処理
</div>

個々の PR の処理中に発生したエラーは捕捉・記録されますが、実行は停止しません。すべての PR の処理が完了したあと、エラーが 1 件でも発生していた場合は `BackportException` が送出されます。CI では、これをトリガーとして `CIBuddy` 経由でチームチャットに通知されます。
