top of page

Grand ménage : comment nous utilisons enfin correctement le cache de GitHub 🧹

Photo du rédacteur: Marcel Dütscher
Marcel Dütscher
27 juil.
5 min de lecture

Dans notre dernier article technique, nous avions mesuré ce que coûte vraiment notre chaîne de compilation et de vérification – et constaté : pas d'argent, mais du temps d'attente et de l'espace de stockage. L'article se terminait par « le grand ménage est prévu, mais pas encore fait ». Finalement, ça a été plus rapide que prévu : depuis ce midi, c'est sur la branche principale. Voici les chiffres.


Le résultat d'abord. L'espace de cache occupé par notre dépôt est passé de 5,32 Go répartis sur 63 entrées à 2,46 Go sur 13. L'analyse de sécurité du code C#, qui tournait jusqu'ici à chaque pull request, y prend désormais 4 secondes au lieu de 4 minutes et demie – tout en continuant à vérifier tout ce qui est livré. Et tout ça sans supprimer une seule des vérifications qui vous protègent.


En bref : à quoi sert un cache ? Chaque build démarre sur une machine fraîchement créée et vide. Pour ne pas repartir de zéro à chaque fois, il peut stocker des résultats intermédiaires – la fois suivante, il les télécharge au lieu de les recréer. Pour cela, on dispose de 10 Go par dépôt. Une fois cet espace plein, GitHub supprime ce qui n'a pas servi depuis le plus longtemps. Et c'était exactement notre problème : la place était bien prise, mais par les mauvaises choses.


Erreur 1 : une clé sans plateforme. Pendant la compilation, Unity crée un énorme dossier intermédiaire. Quatre builds l'utilisent – Windows, Linux, macOS, WebGL – et chacun a besoin du sien. Le build Windows était le seul dont la clé de cache ne contenait aucune indication de plateforme. Il correspondait donc à la première entrée venue, et c'était celle de WebGL : 1,7 Go téléchargés, qu'Unity reconnaissait ensuite, à juste titre, comme « pas à sa place ici », avant de tout réimporter de zéro. Dans le journal : 257 secondes d'import au lieu de 85. La clé s'appelle désormais Library-Windows-, et le fantôme a disparu.


Erreur 2 : personne ne l'a jamais enregistré. La découverte la plus gênante. Les quatre builds essayaient bien sagement de restaurer le dossier intermédiaire – mais aucun workflow ne l'avait jamais écrit. La seule entrée qui existait venait d'un outil qu'on lance à la main. Le cache le plus important du projet était donc entretenu par hasard. 🙈 Désormais, il est écrit exprès.


La décision qui économise de la place : seul le chemin critique compte. Le réflexe aurait été de mettre en cache les quatre plateformes. Un coup d'œil aux temps d'une vraie release montre pourquoi ce serait absurde : les quatre builds démarrent en même temps, Windows termine au bout de 11 minutes, Linux et macOS au bout de 13,7 – et WebGL au bout de 31,6. Tant que WebGL tourne, les autres attendent de toute façon. Seul son cache raccourcit donc l'attente ; les trois autres auraient coûté environ 4,5 Go de notre budget de 10 Go, pour exactement zéro minute gagnée. Nous n'en mettons donc qu'un seul en cache.


Erreur 3 : Docker a dévoré le reste. Lors de la construction de nos images serveur, chaque couche intermédiaire était stockée en cache – environ 3,27 Go répartis sur 24 entrées, soit 64 % de tout le budget, renouvelés à chaque push. Ce déluge chassait à coup sûr le seul cache Unity qui compte. Désormais, seule l'étape finale est stockée, et chacune de nos quatre images a son propre espace de noms – avant, elles en partageaient un seul et n'arrêtaient pas de s'évincer mutuellement. Les 3,27 Go sont devenus environ 150 Mo.


Analyse de sécurité : vérifier là où ça compte. CodeQL, l'outil de GitHub qui traque les failles de sécurité, était notre deuxième plus gros mangeur de temps : 230 minutes en huit jours, presque entièrement pour la partie C#, qui recompile tout le projet pour son analyse – à chaque pull request. Désormais, l'analyse complète tourne lors de la fusion dans la branche principale et une fois par semaine ; les pull requests gardent les vérifications rapides. Mesuré après le changement : 4 secondes dans une pull request, et toujours un peu plus de 4 minutes sur la branche principale. Chaque ligne qui arrive vraiment chez vous est donc toujours vérifiée – simplement plus trois fois de suite.


Des petits riens qui font la différence. Les paquets .NET sont maintenant mis en cache, mais seulement sur le chemin où un humain attend. Un émulateur pour d'autres architectures de processeur n'est plus installé que lorsqu'on compile vraiment pour l'une d'elles. Une commande de nettoyage qui n'avait rien à faire sur une machine fraîchement démarrée, mais coûtait jusqu'à 1,4 minute par build, a disparu. Et les fichiers intermédiaires d'un build de release sont supprimés au bout de 7 jours au lieu de 90 – il s'en était accumulé 703, pour un total de 7,4 Go.


Ce que nous n'avons volontairement pas fait. Deux propositions issues de notre propre analyse ont été abandonnées après mesure, au lieu d'être vendues comme de jolies lignes dans un journal des modifications. Répartir les tests sur deux jobs parallèles aurait fait gagner exactement quatre secondes (une partie prend 2:54, l'autre 4 secondes) – et aurait renommé une vérification obligatoire, laissant chaque pull request bloquée « en attente » pour toujours. Et sauter la grande barrière de tests avant une release n'aurait rien fait gagner non plus, car elle est terminée depuis longtemps pendant que WebGL compile encore. Une optimisation qui ne rapporte rien mais retire un filet de sécurité n'est pas une optimisation.


Et puis, un vrai bug est tombé au passage. En plein ménage, chaque pull request s'est mise à échouer sur une limite de temps : un seul test prenait 212 secondes, alors que 120 sont autorisées. Le deuxième plus lent du même passage : 10,5 secondes. La cause : un chemin d'erreur dans notre serveur qui coupait brutalement une requête HTTP défectueuse au lieu de la terminer proprement. Sous Linux, le système ne récupère ce genre de connexion que lors d'un nettoyage qui passe toutes les deux minutes – et l'arrêt du serveur l'attendait patiemment. Sous Windows, ça ne s'était jamais vu, c'est pourquoi le problème ne frappait que sur le serveur de build. Désormais, le serveur répond dans ce cas avec un code d'erreur honnête et ferme lui-même la connexion : le test passe en un peu plus de 20 secondes – et les vrais joueurs qui tombent sur un tel accroc recevront un message d'erreur compréhensible au lieu d'une connexion coupée net. Une journée de ménage dans la CI qui débusque un bug de production, c'est une bonne journée. 🐛


Pour vous, rien de tout cela ne change directement quoi que ce soit – pas de nouveau bloc, pas de nouvel animal. Cela raccourcit simplement le chemin entre « fini de coder » et « installable chez vous ». Et ça prouve une fois de plus la même phrase que la dernière fois : d'abord mesurer, ensuite toucher. 🚀

Posts récents

Voir tout

Commentaires


Les commentaires sur ce post ne sont plus acceptés. Contactez le propriétaire pour plus d'informations.
bottom of page