Модель релизов
YY.M.patch.build-type, где YY — двузначный год, M — месяц релиза (без ведущего нуля), patch — номер патча в ветке, build — монотонно возрастающий номер сборки, а type — либо stable, либо lts.
Пример: 25.3.8.23-lts — LTS-релиз за март 2025 года, патч 8, сборка 23.
Существует два трека релизов:
- Стабильные релизы публикуются примерно раз в месяц. Патчи выпускаются для трёх последних стабильных релизов, что даёт каждому релизу около трёх месяцев активной поддержки.
- Релизы LTS (Long-Term Support) публикуются в марте и августе каждого года. Одновременно поддерживаются две версии LTS, каждая — не менее 12 месяцев.
Политика бэкпортов
- Исправления уязвимостей — бэкпортируются всегда.
- Исправления критических ошибок (исключения (логические ошибки), потеря данных, некорректные результаты, проблемы с RBAC) — автоматически отбираются для бэкпорта по общим правилам бэкпортирования; определяются по метке
pr-critical-bugfix, из-за которой автоматически добавляетсяpr-must-backport. - Исправления, связанные со стабильностью и регрессиями — бэкпортируются, если риск изменения невелик по сравнению с риском оставить ошибку без исправления; определяются по
pr-must-backport, которую мейнтейнеры добавляют вручную. - Незначительные исправления ошибок при наличии обходного пути — как правило, не бэкпортируются, чтобы не дестабилизировать ветки релиза.
- Новые возможности, улучшения, оптимизации производительности — не бэкпортируются.
pr-must-backport — это ручное переопределение, которое мейнтейнеры используют, чтобы пометить PR для бэкпортирования. Метка pr-critical-bugfix приводит к тому, что pr-must-backport автоматически добавляется хуком CI (см. pr_labels_and_category.py).
Эскалация конфликтов. Если при автоматическом бэкпортировании не удаётся разрешить конфликты слияния, PR cherry-pick всё равно должен быть создан и назначен автору, тому, кто выполнил слияние, и текущим назначенным исполнителям исходного PR, чтобы человек мог разрешить конфликты и завершить бэкпорт.
Инструмент бэкпорта
tests/ci/cherry_pick.py. Инструмент запускается как workflow GitHub Actions в инфраструктуре ClickHouse и охватывает все требования: обнаружение активных веток релиза, выбор PR, подходящих для бэкпорта, выполнение двухэтапной процедуры cherry-pick и бэкпорта, разрешение конфликтов, соблюдение политики задержки и поддержание синхронизации меток.
Долгосрочная цель — выделить эту реализацию в автономный open-source инструмент на Python, который смогут использовать и другие проекты. Целевая архитектура выглядит так:
- Настраиваемый — все параметры политики (подходящие метки, окно задержки, пороги устаревания PR, поведение во время раскатки и т. д.) задаются в виде файла конфигурации, чтобы инструмент можно было адаптировать под требования бэкпорта любого проекта без изменений в коде.
- Распространяемый — упакован как самодостаточный Python wheel, устанавливаемый из PyPI, без зависимости от CI-инфраструктуры ClickHouse.
- Программируемый — предоставляет понятную объектную модель для pull request, меток и веток релиза, чтобы пользователи могли строить собственные workflow поверх базового движка.
Тестирование
- настраиваемым набором веток, представляющих линии релизов,
- pull request’ами с различными комбинациями меток бэкпорта,
- релизными PR с меткой
release, указывающими на ветки релизов.
Активные ветки релиза
release) всё ещё открыт на GitHub. Автоматизация бэкпорта определяет такие ветки динамически при каждом запуске, поэтому при выходе нового релиза или завершении жизненного цикла старого никаких изменений в конфигурации не требуется.
Ветка релиза может находиться в состоянии раскатки (если её релизный PR помечен меткой rolling-out) в период развёртывания нового релиза. Обычные бэкпорты для веток в состоянии раскатки приостанавливаются, чтобы не усложнять этот процесс. Метки, привязанные к конкретной версии (например, v25.3-must-backport), переопределяют это поведение и принудительно запускают бэкпорт даже во время раскатки.
Метка, привязанная к конкретной версии, задаёт самый ранний релиз, в который должен попасть PR: бэкпорт выполняется в этот релиз и в каждую более новую активную ветку релиза, а не только в указанную. Например, v25.3-must-backport у PR, слитого в ветку development, приводит к бэкпорту в 25.3 и во все следующие активные ветки релиза (25.4, 25.5, …). Если присутствует несколько меток для конкретных версий, выбирается самая ранняя версия, поскольку она уже охватывает все более новые.
Указанный релиз сам по себе не обязан быть активным. Метка для релиза, достигшего конца жизненного цикла (то есть без открытого релизного PR), всё равно протягивает исправление вперёд во все активные релизы после него, так что при обновлении с этого релиза исправление не потеряется незаметно. Например, v25.12-must-backport у PR продолжает запускать бэкпорт в 26.1, 26.2, … даже после того, как сам 25.12 достиг конца жизненного цикла.
Реализация
Обзор
CherryPick в GitHub Actions (.github/workflows/cherry_pick.yml), реализованного в tests/ci/cherry_pick.py. Она работает через GitHub API и локальные операции git на самоуправляемом раннере style-checker-aarch64.
Процесс состоит из двух этапов для каждой пары (original PR, ветка релиза):
- Создается PR cherry-pick, чтобы отделить разрешение конфликтов от фактического слияния в целевую ветку. Если конфликтов нет, он сливается автоматически.
- Создается бэкпорт PR в реальную ветку релиза, при этом изменения после cherry-pick объединяются в один коммит.
Метки
Именование веток и PR
N и ветки релиза release/X.Y:
- Ветка для cherry-pick:
cherrypick/release/X.Y/N - Ветка бэкпорта:
backport/release/X.Y/N - Заголовок PR cherry-pick:
Cherry pick #N to release/X.Y: <original title> - Заголовок PR бэкпорта:
Backport #N to release/X.Y: <original title>
Пошаговая инструкция
1
Поиск активных релизов
BackportPRs.receive_release_prs запрашивает у GitHub все открытые PR с меткой release. Head ref этих PR — имена веток релиза (например, release/25.3). На их основе он определяет набор версионно-специфичных меток для поиска: все метки v{VER}-must-backport, существующие в репозитории, чья версия не новее самого нового активного релиза. Более старые метки включаются, даже если их релиз уже не активен (метка новее любого активного релиза пропускается, поскольку она не может быть развёрнута ни в одну активную ветку), поэтому PR, помеченный для релиза с завершённым жизненным циклом, всё равно будет найден, пока существует более новый активный релиз.2
Найдите PR для бэкпорта
BackportPRs.receive_prs_for_backport использует поисковый API GitHub, чтобы найти слитые PR, которые:- имеют как минимум одну метку бэкпорта (
pr-must-backport,pr-must-backport-force,pr-critical-bugfixили метку для конкретной версии), и - не имеют
pr-backports-created, и - были слиты после самой ранней даты коммита, найденной в любой ветке релиза, и
- обновлялись в течение последних 90 дней (чтобы поисковый запрос оставался эффективным).
3
Обработка ветки в состоянии «раскатка»
Когда релизный PR имеет метку
rolling-out, общие метки бэкпорта (pr-must-backport, pr-critical-bugfix) пропускают эту ветку. Бот закрывает все ранее созданные PR cherry-pick или PR бэкпорта для этой ветки с поясняющим комментарием. Метка для конкретной версии (например, v25.3-must-backport) всегда имеет приоритет над этим правилом — для указанного релиза и для каждой более новой активной ветки релиза, на которую она распространяется. pr-must-backport-force обходит проверку rolling-out для всех веток.4
Этап cherry-pick (ReleaseBranch.create_cherrypick)
Для каждой пары (исходный PR, ветка релиза), для которой PR cherry-pick ещё не существует:
- Переключитесь на ветку релиза и создайте от неё ветку бэкпорта (
backport/release/X.Y/N). - Выполните
git merge -s oursотносительно первого родителя коммита слияния, чтобы создать синтетическую базу слияния без изменений содержимого. - Принудительно создайте ветку cherry-pick (
cherrypick/release/X.Y/N), указывающую непосредственно на коммит слияния исходного PR. - Попробуйте выполнить
git merge --no-commit --no-ffветки cherry-pick в ветку бэкпорта:- Если ветка уже в актуальном состоянии, изменение уже присутствует в ветке релиза — пометьте как выполненное и пропустите.
- В противном случае (с конфликтами или без них) выполните reset и отправьте обе ветки.
- Создайте PR cherry-pick из
cherrypick/release/X.Y/Nвbackport/release/X.Y/N, добавив меткиpr-cherrypickиdo not test. - Перенесите
pr-bugfixилиpr-critical-bugfixиз исходного PR, если применимо. - Назначенные исполнители на этом этапе не задаются; их добавляют только при обнаружении конфликтов.
5
Автоматическое слияние PR cherry-pick без конфликтов
Если PR cherry-pick можно смержить (без конфликтов), бот автоматически делает это через GitHub API и сразу переходит к этапу бэкпорта.
6
Этап бэкпорта (ReleaseBranch.create_backport)
После слияния PR cherry-pick:
- Переключитесь на ветку бэкпорта и выполните pull.
- Найдите merge-base между веткой релиза и веткой бэкпорта.
- Выполните
git reset --softк merge-base, объединив все cherry-pick-коммиты в один. - Создайте коммит, используя в качестве сообщения заголовок PR бэкпорта.
- Принудительно отправьте ветку бэкпорта и откройте PR бэкпорта в настоящую ветку релиза.
- Добавьте к PR метку
pr-backport(а такжеpr-bugfix/pr-critical-bugfix, если применимо). - Назначьте в PR автора исходного PR, того, кто выполнил слияние, и текущих назначенных исполнителей (исключая аккаунты роботов).
7
Завершение
Когда бэкпорт выполнен во все ветки релиза для данного исходного PR, бот добавляет
pr-backports-created к исходному PR.8
Предварительная проверка
Прежде чем начинать работу над PR,
ReleaseBranch.pre_check выполняет git merge-base --is-ancestor, чтобы убедиться, что коммит слияния ещё недоступен из ветки релиза. Если доступен, PR считается уже бэкпортированным и пропускается.Обработка устаревших PR cherry-pick
CherryPickPRs запускается в начале каждого ежечасного выполнения и обрабатывает два сценария:
- Осиротевшие PR cherry-pick: если для ветки релиза, связанной с PR cherry-pick, больше нет открытого релизного PR (то есть релиз закрыт), PR cherry-pick закрывается автоматически.
- Повторно открытые PR cherry-pick: если у исходного PR уже есть метка
pr-backports-created, но PR cherry-pick для него всё ещё открыт, меткаpr-backports-createdудаляется из исходного PR, чтобы его можно было обработать повторно.
- Через 3 дня без обновлений бот публикует комментарий с Ping и упоминанием назначенных исполнителей.
- Через 7 дней без обновлений бот публикует комментарий о закрытии и закрывает PR.
Разрешение конфликтов
- Удалите метку
pr-cherrypickиз PR cherry-pick. - Удалите ветку
cherrypick/.... - Удалите
pr-backports-createdиз исходного PR, если он есть.
CI для PR бэкпорта
BackportPR, определённый в ci/workflows/backport_branches.py), а не стандартный workflow для pull request. Этот workflow запускает репрезентативное подмножество CI: сборки ASan/UBSan и TSan, релизные сборки, сборки для macOS, функциональные тесты под ASan, стресс-тесты под TSan и интеграционные тесты. Он проверяет, что ветка бэкпорта содержит от 1 до 50 коммитов и как минимум один изменённый файл (это проверяется скриптом check_backport_branch.py).
Аутентификация
ROBOT_CLICKHOUSE_SSH_KEY). Вызовы GitHub API аутентифицируются через get_best_robot_token, который выбирает токен с наибольшей оставшейся квотой из пула, хранящегося в SSM (/github-tokens). ROBOT_CLICKHOUSE_COMMIT_TOKEN используется на шаге checkout в workflow Actions, а не для вызовов API. Аккаунты роботов (robot-clickhouse, clickhouse-gh) исключаются при назначении ответственного.
Кэш API GitHub
GitHubCache (из cache_utils.py) сохраняет кэш объектов PyGithub в S3, сокращая количество вызовов API при ежечасных запусках. Кэш загружается в начале и выгружается в конце каждого запуска.
Обработка ошибок
BackportException. В CI это приводит к отправке уведомления в командный чат через CIBuddy.