Modelo de versiones
YY.M.patch.build-type, donde YY es el año con dos dígitos, M es el mes de lanzamiento (sin cero a la izquierda), patch es el número de parche dentro de la rama, build es un número de compilación que aumenta de forma monotónica y type es stable o lts.
Ejemplo: 25.3.8.23-lts — LTS de marzo de 2025, parche 8, compilación 23.
Hay dos líneas de versiones:
- Las versiones estables se publican aproximadamente una vez al mes. Las tres versiones estables más recientes reciben parches, lo que supone aproximadamente tres meses de soporte activo por versión.
- Las versiones LTS (Long-Term Support) se publican en marzo y agosto de cada año. Se admiten simultáneamente dos versiones LTS, cada una durante al menos 12 meses.
Política de backport
- Correcciones de seguridad — siempre se incluyen en backport.
- Correcciones de errores críticos (exceptions (errores lógicos), pérdida de datos, resultados incorrectos, problemas de RBAC) — se seleccionan automáticamente para backport según las reglas generales; se identifican con la etiqueta
pr-critical-bugfix, que hace quepr-must-backportse añada automáticamente. - Correcciones de estabilidad y regresiones — se llevan a backport cuando el riesgo del cambio es bajo en comparación con el riesgo de dejar el error sin corregir; se identifican con
pr-must-backport, añadida manualmente por los mantenedores. - Correcciones de errores menores con una solución alternativa disponible — por lo general, no se incluyen en backport para evitar desestabilizar las ramas de lanzamiento.
- Nuevas funcionalidades, mejoras y trabajo de rendimiento — no se llevan a backport.
pr-must-backport es la anulación manual que usan los mantenedores para marcar una PR para backport. La etiqueta pr-critical-bugfix hace que pr-must-backport se añada automáticamente mediante el hook de CI (consulta pr_labels_and_category.py).
Escalado de conflictos. Cuando el backport automático no puede resolver conflictos de merge, igualmente debe crearse una PR de cherry-pick y asignarse al autor, a quien hizo el merge y a los asignados existentes de la PR original para que una persona pueda resolver los conflictos y completar el backport.
Herramienta de backport
tests/ci/cherry_pick.py. La herramienta se ejecuta como un flujo de trabajo de GitHub Actions en la infraestructura de ClickHouse y cubre todos los requisitos: detectar las ramas de lanzamiento activas, seleccionar los PR aptos para backport, realizar el procedimiento de cherry-pick y backport en dos fases, gestionar conflictos, aplicar la política de demora y mantener las etiquetas sincronizadas.
El objetivo a largo plazo es extraer esta implementación y convertirla en una herramienta independiente de Python de código abierto que otros proyectos puedan adoptar. El diseño previsto es:
- Configurable — todos los parámetros de la política (etiquetas válidas, ventana de demora, umbrales de PR obsoletos, comportamiento durante el rolling-out, etc.) se expresan en un archivo de configuración para que la herramienta pueda adaptarse a los requisitos de backport de cualquier proyecto sin necesidad de cambiar el código.
- Distribuible — empaquetada como un wheel de Python autocontenido e instalable desde PyPI, sin depender de la infraestructura de CI de ClickHouse.
- Programable — expone un modelo de objetos claro para pull requests, etiquetas y ramas de lanzamiento, de modo que los usuarios puedan crear flujos de trabajo personalizados sobre el motor principal.
Pruebas
- un conjunto configurable de ramas que representan líneas de versión,
- pull requests con varias combinaciones de etiquetas de backport,
- PR de lanzamiento con la etiqueta
releaseque apuntan a las ramas de lanzamiento.
Ramas de lanzamiento activas
release) sigue abierta en GitHub. La automatización de backport las detecta dinámicamente en cada ejecución, por lo que no es necesario realizar cambios de configuración cuando se crea una nueva versión o una antigua llega al fin de su vida útil.
Una rama de lanzamiento puede estar en estado rolling-out (su PR de lanzamiento lleva la etiqueta rolling-out) durante el período en que se está desplegando una nueva versión. Los backports generales se pausan en las ramas en rolling-out para evitar complicar el despliegue. Las etiquetas específicas de versión (por ejemplo, v25.3-must-backport) prevalecen sobre esto y fuerzan el backport incluso durante un despliegue.
Una etiqueta específica de versión establece la versión de lanzamiento más antigua a la que debe llegar la PR: se aplica backport a esa versión y a todas las ramas de lanzamiento activas más recientes, no solo a la indicada. Por ejemplo, v25.3-must-backport en una PR fusionada en la rama de desarrollo aplica backport a 25.3 y a todas las versiones activas posteriores (25.4, 25.5, …). Si hay varias etiquetas específicas de versión, prevalece la versión más baja, ya que ya cubre las más recientes.
La versión indicada no tiene que estar activa. Una etiqueta para una versión al fin de su vida útil (una sin ninguna PR de lanzamiento abierta) sigue trasladando la corrección a todas las versiones activas posteriores, de modo que al actualizar desde esa versión nunca se pierda la corrección de forma silenciosa. Por ejemplo, v25.12-must-backport en una PR sigue aplicando backport a 26.1, 26.2, … incluso después de que la propia 25.12 haya llegado al fin de su vida útil.
Implementación
Descripción general
CherryPick de GitHub Actions (.github/workflows/cherry_pick.yml), implementado en tests/ci/cherry_pick.py. Opera mediante la API de GitHub y operaciones locales de git en un runner style-checker-aarch64 autoalojado.
El proceso consta de dos etapas para cada par (original PR, release branch):
- Se crea una cherry-pick PR para aislar la resolución de conflictos del destino real del merge. Si no hay conflictos, se hace merge automáticamente.
- Se crea una backport PR contra la release branch real, con los cambios del cherry-pick compactados en un único commit.
Etiquetas
Nomenclatura de ramas y PR
N y rama de lanzamiento release/X.Y:
- Rama de cherry-pick:
cherrypick/release/X.Y/N - Rama de backport:
backport/release/X.Y/N - Título del PR de cherry-pick:
Cherry pick #N to release/X.Y: <original title> - Título del PR de backport:
Backport #N to release/X.Y: <original title>
Proceso paso a paso
1
Detectar las versiones activas
BackportPRs.receive_release_prs consulta GitHub para obtener todos los PR abiertos con la etiqueta release. Las referencias head de estos PR son los nombres de las ramas de lanzamiento (por ejemplo, release/25.3). A partir de ellas, deriva el conjunto de etiquetas específicas de versión que debe buscar: cada etiqueta v{VER}-must-backport que exista en el repositorio y cuya versión no sea más reciente que la release activa más nueva. Se incluyen las etiquetas más antiguas incluso cuando su release ya no está activa (se omite una etiqueta más reciente que todas las releases activas, ya que no podría expandirse a ninguna rama activa), por lo que un PR etiquetado para una release en fin de su vida útil sigue encontrándose siempre que haya una release más reciente activa.2
Buscar PR para backport
BackportPRs.receive_prs_for_backport usa la API de búsqueda de GitHub para encontrar PR fusionados que:- tengan al menos una etiqueta de backport (
pr-must-backport,pr-must-backport-force,pr-critical-bugfixo una etiqueta específica de versión), y - no tengan ya
pr-backports-created, y - se hayan fusionado después de la fecha del commit más antiguo encontrada en cualquier rama de lanzamiento, y
- se hayan actualizado en los últimos 90 días (para que la consulta de búsqueda siga siendo eficiente).
3
Gestión de ramas durante el despliegue
Cuando un PR de lanzamiento lleva la etiqueta
rolling-out, las etiquetas generales de backport (pr-must-backport, pr-critical-bugfix) omiten esa rama. El bot cierra cualquier PR de cherry-pick o backport creado previamente para esa rama con un comentario explicativo. Una etiqueta específica de versión (por ejemplo, v25.3-must-backport) siempre prevalece sobre esto — para la versión indicada y para cada rama de lanzamiento activa más reciente a la que se extienda. pr-must-backport-force omite la comprobación de rolling-out para todas las ramas.4
Etapa de cherry-pick (ReleaseBranch.create_cherrypick)
Para cada par (PR original, rama de lanzamiento) en el que aún no exista un PR de cherry-pick:
- Haz checkout de la rama de lanzamiento y crea una rama de backport (
backport/release/X.Y/N) a partir de ella. - Ejecuta
git merge -s ourscontra el primer padre del commit de merge para crear una base de merge sintética sin cambios de contenido. - Fuerza la creación de una rama de cherry-pick (
cherrypick/release/X.Y/N) que apunte directamente al commit de merge del PR original. - Intenta hacer
git merge --no-commit --no-ffde la rama de cherry-pick en la rama de backport:- Si ya está al día, el cambio ya está presente en la rama de lanzamiento — márcalo como completado y omítelo.
- En caso contrario (con o sin conflictos), haz reset y push de ambas ramas.
- Crea el PR de cherry-pick dirigido a
backport/release/X.Y/Ndesdecherrypick/release/X.Y/N, con las etiquetaspr-cherrypickydo not test. - Propaga
pr-bugfixopr-critical-bugfixdesde el PR original, si corresponde. - Las personas asignadas no se establecen en este punto; solo se añaden cuando se detectan conflictos.
5
Merge automático de PR de cherry-pick sin conflictos
Si el PR de cherry-pick puede fusionarse (sin conflictos), el bot lo fusiona automáticamente a través de la API de GitHub y pasa de inmediato a la etapa de backport.
6
Etapa de backport (ReleaseBranch.create_backport)
Después de que se haya hecho merge del PR de cherry-pick:
- Haz checkout y pull de la rama de backport.
- Encuentra la base de merge entre la rama de lanzamiento y la rama de backport.
- Haz
git reset --softa la base de merge, compactando todos los commits de cherry-pick en uno. - Haz commit usando como mensaje el título del PR de backport.
- Haz force-push de la rama de backport y abre un PR de backport dirigido a la rama de lanzamiento real.
- Etiqueta el PR con
pr-backport(ypr-bugfix/pr-critical-bugfixsi corresponde). - Asigna el PR al autor del PR original, a quien hizo el merge y a las personas asignadas existentes (excluyendo las cuentas de robot).
7
Finalización
Cuando se hayan aplicado mediante backport todas las ramas de lanzamiento de un PR original determinado, el bot añade
pr-backports-created al PR original.8
Verificación previa
Antes de empezar cualquier trabajo en un PR,
ReleaseBranch.pre_check ejecuta git merge-base --is-ancestor para verificar que el commit de merge no sea ya accesible desde la rama de lanzamiento. Si lo es, se considera que al PR ya se le ha aplicado backport y se omite.Gestión de PR de cherry-pick obsoletos
CherryPickPRs se ejecuta al inicio de cada ejecución horaria y gestiona dos escenarios:
- PR de cherry-pick huérfanos: Si la rama de lanzamiento de un PR de cherry-pick ya no tiene un PR de lanzamiento abierto (es decir, la versión está cerrada), el PR de cherry-pick se cierra automáticamente.
- PR de cherry-pick reabiertos: Si un PR original ya tiene
pr-backports-created, pero un PR de cherry-pick asociado sigue abierto, la etiquetapr-backports-createdse elimina del PR original para que pueda volver a procesarse.
- Después de 3 días sin actualizaciones, el bot publica un comentario de recordatorio mencionando a las personas asignadas.
- Después de 7 días sin actualizaciones, el bot publica un comentario de cierre y cierra el PR.
Resolución de conflictos
cherry-pick tiene conflictos, el PR de cherry-pick se deja abierto para que una persona los resuelva. El bot se lo asigna al autor del PR original, a quien lo fusionó y a las personas asignadas. Después de resolver los conflictos y de fusionar el PR de cherry-pick, el bot crea el PR de backport en la siguiente ejecución horaria.
Para descartar por completo un backport, cierra el PR de cherry-pick. El bot lo considerará omitido intencionalmente.
Para recrear desde cero un PR de cherry-pick dañado:
- Elimina la etiqueta
pr-cherrypickdel PR decherry-pick. - Elimina la rama
cherrypick/.... - Elimina
pr-backports-createddel PR original si está presente.
CI para PR de backport
BackportPR, definido en ci/workflows/backport_branches.py) en lugar del flujo de trabajo estándar para pull requests. Este flujo de trabajo ejecuta un subconjunto representativo de CI: compilaciones con ASan/UBSan y TSan, compilaciones de lanzamiento, compilaciones para macOS, pruebas funcionales con ASan, pruebas de estrés con TSan y pruebas de integración. Valida que la rama de backport tenga entre 1 y 50 commits y al menos un archivo modificado (lo verifica check_backport_branch.py).
Autenticación
ROBOT_CLICKHOUSE_SSH_KEY) para las operaciones de git push. Las llamadas a la API de GitHub se autentican mediante get_best_robot_token, que selecciona el token con más cuota disponible de un conjunto almacenado en SSM (/github-tokens). ROBOT_CLICKHOUSE_COMMIT_TOKEN se usa en el paso de checkout del flujo de trabajo de Actions, no para las llamadas a la API. Las cuentas de robot (robot-clickhouse, clickhouse-gh) se excluyen al asignar la persona responsable.
Caché de la API de GitHub
GitHubCache (de cache_utils.py) guarda de forma persistente la caché de objetos de PyGithub en S3, lo que reduce las llamadas a la API en las ejecuciones horarias. La caché se descarga al inicio y se sube al final de cada ejecución.
Manejo de errores
BackportException. En CI, esto desencadena una notificación a través de CIBuddy en el chat del equipo.