Modèle de publication
YY.M.patch.build-type, où YY correspond à l’année sur deux chiffres, M au mois de release (sans zéro initial), patch au numéro de correctif dans la branche, build à un numéro de build croissant, et type vaut soit stable, soit lts.
Exemple : 25.3.8.23-lts — LTS de mars 2025, correctif 8, build 23.
Il existe deux cycles de publication :
- Les versions stables sont publiées environ une fois par mois. Les trois versions stables les plus récentes reçoivent des correctifs, ce qui représente environ trois mois de support actif par version.
- Les versions LTS (support à long terme) sont publiées en mars et en août chaque année. Deux versions LTS sont prises en charge simultanément, chacune pendant au moins 12 mois.
Politique de backport
- Correctifs de sécurité — toujours backportés.
- Correctifs de bugs critiques (exceptions (erreurs logiques), perte de données, résultats erronés, problèmes RBAC) — automatiquement sélectionnés pour le backport selon les règles générales de backport ; identifiés par le label
pr-critical-bugfix, qui déclenche l’ajout automatique depr-must-backport. - Correctifs de stabilité et de régression — backportés lorsque le risque du changement est faible par rapport au risque de laisser le bug en place ; identifiés par
pr-must-backport, ajouté manuellement par les mainteneurs. - Correctifs de bugs mineurs avec une solution de contournement disponible — généralement non backportés afin d’éviter de déstabiliser les branches de release.
- Nouvelles fonctionnalités, améliorations, optimisations des performances — non backportées.
pr-must-backport est le mécanisme de forçage manuel utilisé par les mainteneurs pour marquer une PR à backporter. Le label pr-critical-bugfix déclenche l’ajout automatique de pr-must-backport par le hook CI (voir pr_labels_and_category.py).
Escalade des conflits. Lorsque le backport automatique ne peut pas résoudre les conflits de fusion, une PR de cherry-pick doit tout de même être créée et assignée à l’auteur, à la personne qui a fusionné la PR, ainsi qu’aux personnes déjà assignées à la PR d’origine, afin qu’une personne puisse résoudre les conflits et finaliser le backport.
Outil de backport
tests/ci/cherry_pick.py. L’outil s’exécute sous forme de workflow GitHub Actions sur l’infrastructure ClickHouse et couvre l’ensemble des besoins : détection des branches de release actives, sélection des PR éligibles au backport, exécution de la procédure de cherry-pick et de backport en deux étapes, gestion des conflits, application de la politique de délai et synchronisation des labels.
L’objectif à long terme est d’extraire cette implémentation dans un outil Python open source autonome que d’autres projets pourront adopter. La conception visée est la suivante :
- Configurable — tous les paramètres de la politique (labels éligibles, fenêtre de délai, seuils de PR stale, comportement pendant le rolling-out, etc.) sont définis dans un fichier de configuration afin que l’outil puisse être adapté aux exigences de backport de n’importe quel projet sans modification du code.
- Distribuable — empaqueté sous la forme d’une wheel Python autonome installable depuis PyPI, sans dépendre de l’infrastructure CI de ClickHouse.
- Programmable — expose un modèle objet clair pour les pull requests, les labels et les branches de release afin que les utilisateurs puissent créer des workflows personnalisés par script au-dessus du moteur principal.
Tests
- un ensemble configurable de branches représentant les lignes de release,
- des pull requests portant différentes combinaisons de labels de backport,
- des PR de release avec le label
releasepointant vers les branches de release.
Branches de release actives
release) est encore ouverte sur GitHub. L’automatisation des backports les détecte dynamiquement à chaque exécution ; aucune modification de configuration n’est donc nécessaire lorsqu’une nouvelle release est publiée ou qu’une ancienne atteint sa fin de vie.
Une branche de release peut être dans l’état rolling-out (sa release PR porte le label rolling-out) pendant la période de déploiement d’une nouvelle release. Les backports généraux sont alors mis en pause pour les branches en rolling-out afin de ne pas compliquer le rollout. Les labels spécifiques à une version (par ex. v25.3-must-backport) priment toutefois sur ce comportement et forcent le backport même pendant un rollout.
Un label spécifique à une version définit la release la plus ancienne que la PR doit atteindre : elle est backportée vers cette release ainsi que vers chaque branche de release active plus récente, et pas uniquement vers celle qui est nommée. Par exemple, v25.3-must-backport sur une PR fusionnée dans la branche de développement est backportée vers 25.3 ainsi que vers chaque release active ultérieure (25.4, 25.5, …). Si plusieurs labels spécifiques à une version sont présents, c’est la version la plus basse qui l’emporte, puisqu’elle couvre déjà les plus récentes.
La release nommée n’a pas besoin d’être elle-même active. Un label visant une release en fin de vie (sans release PR ouverte) propage malgré tout le correctif vers chaque release active ultérieure, de sorte qu’une mise à niveau depuis cette release ne perde jamais silencieusement le correctif. Par exemple, v25.12-must-backport sur une PR continue d’être backporté vers 26.1, 26.2, … même après que 25.12 elle-même a atteint sa fin de vie.
Implémentation
Vue d’ensemble
CherryPick (.github/workflows/cherry_pick.yml), implémenté dans tests/ci/cherry_pick.py. Elle s’appuie sur l’API GitHub et sur des opérations git locales sur un runner style-checker-aarch64 self-hosted.
Le processus se déroule en deux étapes pour chaque paire (PR d’origine, branche de release) :
- Une PR de cherry-pick est créée afin d’isoler la résolution des conflits de la véritable cible de fusion. En l’absence de conflits, elle est fusionnée automatiquement.
- Une PR de backport est créée sur la branche de release réelle, avec les modifications issues du cherry-pick regroupées en un seul commit.
Labels
Nommage des branches et des PR
N et chaque branche de release release/X.Y :
- Branche de cherry-pick :
cherrypick/release/X.Y/N - Branche de backport :
backport/release/X.Y/N - Titre de la PR de cherry-pick :
Cherry pick #N to release/X.Y: <original title> - Titre de la PR de backport :
Backport #N to release/X.Y: <original title>
Procédure étape par étape
1
Détecter les versions actives
BackportPRs.receive_release_prs interroge GitHub pour toutes les PR ouvertes portant le label release. Les références head de ces PR correspondent aux noms des branches de release (par exemple release/25.3). À partir de celles-ci, il déduit l’ensemble des labels spécifiques à la version à rechercher : chaque label v{VER}-must-backport qui existe dans le dépôt et dont la version n’est pas plus récente que la release active la plus récente. Les labels plus anciens sont inclus même lorsque leur release n’est plus active (un label plus récent que toutes les releases actives est ignoré, puisqu’il ne pourrait s’étendre à aucune branche active), de sorte qu’une PR portant un label pour une release en fin de vie est tout de même trouvée tant qu’une release plus récente est active.2
Identifier les PR à backporter
BackportPRs.receive_prs_for_backport utilise l’API de recherche GitHub pour trouver les PR fusionnées qui :- portent au moins un label de backport (
pr-must-backport,pr-must-backport-force,pr-critical-bugfixou un label spécifique à la version), et - n’ont pas déjà le label
pr-backports-created, et - ont été fusionnées après la date du commit le plus ancien trouvée sur une branche de release, et
- ont été mises à jour au cours des 90 derniers jours (afin de préserver l’efficacité de la query de recherche).
3
Gestion des branches pendant le déploiement
Lorsqu’une PR de release porte le label
rolling-out, les labels de backport généraux (pr-must-backport, pr-critical-bugfix) ignorent cette branche. Le bot ferme toutes les PR de cherry-pick ou de backport précédemment créées pour cette branche en y ajoutant un commentaire explicatif. Un label spécifique à la version (par exemple v25.3-must-backport) prévaut toujours sur ce comportement — pour la release ainsi nommée comme pour chaque branche de release active plus récente à laquelle il s’étend. pr-must-backport-force contourne la vérification rolling-out pour toutes les branches.4
Étape de cherry-pick (ReleaseBranch.create_cherrypick)
Pour chaque paire (PR d’origine, branche de release) pour laquelle aucune PR de cherry-pick n’existe encore :
- Placez-vous sur la branche de release et créez une branche de backport (
backport/release/X.Y/N) à partir de celle-ci. - Exécutez
git merge -s ourssur le premier parent du commit de fusion afin de créer une base de fusion synthétique sans modification de contenu. - Créez de force une branche de cherry-pick (
cherrypick/release/X.Y/N) pointant directement vers le commit de fusion de la PR d’origine. - Tentez un
git merge --no-commit --no-ffde la branche de cherry-pick dans la branche de backport :- Si tout est déjà à jour, cela signifie que la modification est déjà présente dans la branche de release — marquez l’opération comme terminée et passez à la suite.
- Sinon (avec ou sans conflits), réinitialisez et poussez les deux branches.
- Créez la PR de cherry-pick ciblant
backport/release/X.Y/Ndepuischerrypick/release/X.Y/N, avec les labelspr-cherrypicketdo not test. - Propagez
pr-bugfixoupr-critical-bugfixdepuis la PR d’origine, le cas échéant. - Les assignés ne sont pas définis à ce stade ; ils ne sont ajoutés qu’en cas de conflits détectés.
5
Fusion automatique des PR de cherry-pick sans conflit
Si la PR de cherry-pick est fusionnable (sans conflits), le bot la fusionne automatiquement via l’API GitHub et passe immédiatement à l’étape de backport.
6
Étape de backport (ReleaseBranch.create_backport)
Après la fusion de la PR de cherry-pick :
- Placez-vous sur la branche de backport et récupérez les dernières modifications.
- Trouvez la base de fusion entre la branche de release et la branche de backport.
- Exécutez
git reset --softjusqu’à la base de fusion afin de regrouper tous les commits cherry-pickés en un seul. - Effectuez un commit en utilisant le titre de la PR de backport comme message.
- Force-poussez la branche de backport et ouvrez une PR de backport ciblant la branche de release réelle.
- Ajoutez à la PR le label
pr-backport(ainsi quepr-bugfix/pr-critical-bugfix, le cas échéant). - Assignez la PR à l’auteur de la PR d’origine, à la personne qui l’a fusionnée et aux assignés existants (hors comptes robotisés).
7
Finalisation
Lorsque toutes les branches de release associées à une PR d’origine donnée ont fait l’objet d’un backport, le bot ajoute
pr-backports-created à la PR d’origine.8
Vérification préalable
Avant d’entamer tout travail sur une PR,
ReleaseBranch.pre_check exécute git merge-base --is-ancestor afin de vérifier que le commit de fusion n’est pas déjà présent dans l’historique de la branche de release. Si c’est le cas, la PR est considérée comme déjà backportée et ignorée.Gestion des cherry-pick PR obsolètes
CherryPickPRs s’exécute au début de chaque exécution horaire et gère deux scénarios :
- cherry-pick PR orphelines : si la branche de release d’une cherry-pick PR n’a plus de release PR ouverte (c.-à-d. que la release est clôturée), la cherry-pick PR est automatiquement fermée.
- cherry-pick PR rouvertes : si une PR d’origine a déjà
pr-backports-created, mais qu’une cherry-pick PR correspondante est toujours ouverte, le labelpr-backports-createdest supprimé de la PR d’origine afin qu’elle puisse être retraitée.
- Après 3 jours sans mise à jour, le bot publie un commentaire de relance en mentionnant les personnes assignées.
- Après 7 jours sans mise à jour, le bot publie un commentaire de fermeture et ferme la PR.
Résolution des conflits
- Supprimez le label
pr-cherrypickde la cherry-pick PR. - Supprimez la branche
cherrypick/.... - Supprimez
pr-backports-createdde la PR d’origine, s’il est présent.
CI pour les PR de backport
BackportPR, défini dans ci/workflows/backport_branches.py) plutôt que le workflow standard pour les pull requests. Ce workflow exécute un sous-ensemble représentatif de la CI : builds ASan/UBSan et TSan, builds de release, builds macOS, tests fonctionnels sous ASan, tests de stress sous TSan et tests d’intégration. Il vérifie que la branche de backport contient entre 1 et 50 commits et au moins un fichier modifié (contrôle assuré par check_backport_branch.py).
Authentification
ROBOT_CLICKHOUSE_SSH_KEY) pour les opérations de push git. Les appels à l’API GitHub sont authentifiés via get_best_robot_token, qui sélectionne le token disposant du quota restant le plus élevé dans un pool stocké dans SSM (/github-tokens). ROBOT_CLICKHOUSE_COMMIT_TOKEN est utilisé par l’étape checkout du workflow Actions, et non pour les appels à l’API. Les comptes robot (robot-clickhouse, clickhouse-gh) sont exclus lors de l’attribution d’un responsable.
Cache de l’API GitHub
GitHubCache (dans cache_utils.py) enregistre le cache d’objets PyGithub dans S3, ce qui réduit le nombre d’appels à l’API d’une exécution horaire à l’autre. Le cache est téléchargé au début et envoyé à la fin de chaque exécution.
Gestion des erreurs
BackportException est levée. En CI, cela déclenche une notification via CIBuddy dans le canal de discussion de l’équipe.