Optimiser les fusions de Monorepos : Stratégies pour des Builds Verts
Pour de nombreuses petites équipes de développement logiciel ou celles gérant plusieurs dépôts de code indépendants, l’acte de fusionner de nouvelles modifications de code dans la base de code principale semble simple : un simple clic sur un bouton. Pourtant, cette tâche apparemment triviale se transforme en l’un des goulots d’étranglement les plus importants de la chaîne de livraison logicielle pour les grandes organisations opérant au sein d’une base de code unique et partagée, communément appelée monorepo. Ici, des dizaines, voire des centaines d’ingénieurs contribuent simultanément, augmentant la complexité de l’intégration.
Bien que le débat entre monorepos et polyrepos soit vaste, il est clair que les monorepos offrent des avantages distincts, tels que l’identification et la correction simplifiées des vulnérabilités, un refactoring inter-projets plus facile, et des outils cohérents ainsi que des bibliothèques partagées à travers divers projets. Cependant, ces avantages s’accompagnent de défis inhérents. Les développeurs rencontrent fréquemment des dépendances obsolètes dues à des demandes de tirage (PRs) basées sur des branches principales dépassées, des conflits subtils découlant d’un travail simultané sur un code similaire, et des problèmes d’infrastructure persistants comme les délais d’attente. De plus, la gestion des dépendances internes et tierces devient complexe, et les états partagés peuvent entraîner des comportements de test incohérents ou “flaky”. À mesure qu’une organisation d’ingénierie prend de l’ampleur, ces défis se multiplient de manière exponentielle, conduisant souvent les développeurs à passer des heures improductives simplement à attendre que les processus de build se terminent avec succès.
Pour atténuer ces retards croissants, les flux de travail de développement modernes ont de plus en plus adopté des outils d’automatisation des fusions, tels que les Files de Fusion GitHub, les Trains de Fusion GitLab et des solutions similaires. Ces systèmes changent fondamentalement la donne en introduisant des portails automatisés qui régulent le flux des changements vers la base de code principale. Le processus implique généralement qu’un développeur marque une demande de tirage comme prête pour l’intégration. Le système rebase ensuite automatiquement cette PR sur la toute dernière version de la branche principale. Un processus d’intégration continue (CI) est alors déclenché dans ce contexte mis à jour. Si les vérifications CI passent, le système procède à la fusion des changements. Il est crucial de noter que si de nouvelles PRs arrivent pendant qu’une exécution CI est en cours, elles sont mises en file d’attente et attendent leur tour, garantissant une séquence d’intégration ordonnée et validée.
Bien que les files de fusion constituent une solution fondamentale, le volume considérable de changements dans les grands monorepos nécessite une optimisation supplémentaire. Une stratégie courante est le regroupement des demandes de tirage. Au lieu de traiter une PR à la fois, plusieurs sont regroupées puis soumises à une seule exécution CI. Cette approche réduit considérablement le nombre total d’exécutions CI et raccourcit les temps d’attente globaux. Si le processus CI pour un lot réussit, toutes les PRs incluses sont fusionnées simultanément. En cas d’échec au sein d’un lot, le système peut systématiquement “bisecter” le lot pour identifier la PR problématique, permettant aux changements réussis restants de se poursuivre. Bien que le regroupement puisse réduire considérablement les temps de fusion dans des conditions idéales — par exemple, en réduisant hypothétiquement une attente de 50 heures à 12,5 heures avec une taille de lot de quatre — les scénarios réels avec un taux d’échec modeste de 10 % peuvent prolonger considérablement le temps total de fusion, potentiellement le doubler, et augmenter le nombre d’exécutions CI en raison du traitement répété.
Pour pousser l’efficacité plus loin, les files optimistes introduisent un changement de paradigme, passant d’un modèle de traitement sériel à une approche plus parallèle. Plutôt que d’attendre que le processus CI d’une demande de tirage soit entièrement terminé, le système suppose de manière optimiste qu’il réussira. Il crée alors une branche principale alternative pour commencer immédiatement le processus CI pour la prochaine demande de tirage dans la file d’attente. Si la PR initiale passe son CI, elle fusionne dans la branche principale ; de même, la PR suivante fusionne après sa réussite. Si la première PR échoue, la branche principale alternative est écartée, et une nouvelle est créée sans le changement problématique, permettant au processus de validation de redémarrer pour les PRs restantes. La combinaison de cette approche “optimiste” avec le regroupement conduit au regroupement optimiste, où des groupes entiers de PRs sont traités en parallèle, les échecs entraînant un mécanisme de division et d’identification.
Une autre technique avancée est la modélisation prédictive. Cela implique d’analyser les données historiques et les caractéristiques des demandes de tirage — telles que les lignes de code modifiées, les types de fichiers modifiés ou le nombre de dépendances — pour calculer un score indiquant la probabilité de succès ou d’échec. En tirant parti de ces prédictions, le système peut prioriser ou réorganiser les PRs, concentrant les ressources CI sur les chemins les plus susceptibles de réussir, réduisant ainsi les coûts CI globaux et accélérant les fusions.
Pour les monorepos véritablement massifs, une seule file d’attente peut encore devenir un goulot d’étranglement. Ceci est résolu par les multi-files et les cibles affectées. Les outils de build de monorepo modernes comme Bazel, Nx ou Turborepo peuvent identifier précisément quelles parties de la base de code sont impactées par un changement spécifique. Cette intelligence permet au système de regrouper les demandes de tirage dans des files d’attente indépendantes et parallèles basées sur les “cibles affectées”. Par exemple, si un système produit quatre types de build distincts (A, B, C, D) et que les PRs entrantes n’affectent qu’un sous-ensemble de ceux-ci, des files d’attente séparées peuvent être établies pour chaque type de build. Cela garantit que les changements non liés ne se bloquent pas mutuellement, accélérant considérablement le processus d’intégration global en permettant l’exécution concurrente de builds indépendants.
Au-delà de ces stratégies basées sur les files d’attente, d’autres optimisations complémentaires améliorent l’efficacité du flux de travail. La réorganisation des changements implique de prioriser les demandes de tirage présentant un risque de défaillance plus faible ou une importance commerciale plus élevée, en les plaçant plus tôt dans la file d’attente pour minimiser les défaillances en cascade. Les changements plus complexes ou incertains sont planifiés ultérieurement. Le principe “échouer rapidement” dicte de prioriser l’exécution des tests les plus susceptibles d’échouer tôt dans le processus CI, garantissant que les changements problématiques sont identifiés et traités rapidement. Enfin, le fractionnement de l’exécution des tests peut impliquer l’exécution d’un ensemble de tests rapides et critiques avant la fusion pour détecter les problèmes courants, tandis que des tests plus étendus ou plus lents (comme les tests d’intégration ou de fumée) sont exécutés après la fusion. Dans le rare cas d’un échec après la fusion, un mécanisme de retour arrière automatisé peut atténuer le risque.
En fin de compte, l’orchestration sophistiquée de ces stratégies de fusion vise à trouver un équilibre crucial : maintenir une fiabilité et une qualité de code élevées tout en maximisant simultanément la vitesse de publication. Au-delà de la simple économie de cycles CI, l’automatisation avancée des fusions réduit considérablement les temps d’attente des développeurs, accélère la livraison des fonctionnalités et préserve la sérénité des équipes d’ingénierie naviguant dans les complexités du développement logiciel à grande échelle.