Nous avons récemment lancé un petit projet pour nettoyer la façon dont certaines parties de nos systèmes communiquent en coulisses chez Buffer.
Un peu de contexte : nous utilisons quelque chose appelé SQS (Amazon Simple Queue Service. Ces files d'attente agissent comme des salles d'attente pour les tâches. Une partie de notre système dépose un message et une autre le récupère plus tard. Pensez-y comme si vous laissiez une note à un collègue : « Hé, quand vous en avez l'occasion, traitez ces données. » Le système qui envoie la note n'a pas à attendre une réponse.
Notre projet consistait à effectuer une maintenance de routine : mettre à jour les outils que nous utilisons pour tester les files d'attente localement et nettoyer leur configuration.
Mais pendant que nous cartographions les files d'attente que nous utilisons réellement, nous avons découvert quelque chose auquel nous ne nous attendions pas : sept processus d'arrière-plan différents (ou tâches cron, qui sont des tâches planifiées qui s'exécutent automatiquement) et des travailleurs qui s'exécutaient en silence depuis cinq ans. Tous ne font absolument rien d’utile.
Voici pourquoi c'est important, comment nous les avons trouvés et ce que nous avons fait à ce sujet.
Pourquoi c'est plus important que tu ne le penses
Oui, gérer des infrastructures inutiles coûte de l’argent. J'ai fait un calcul rapide et pour l'un de ces travailleurs, nous aurions payé environ 360 à 600 $ sur 5 ans. Il s’agit d’une somme modeste dans le grand schéma de nos finances, mais certainement d’un pur gaspillage pour un processus qui ne fait rien.
Cependant, après avoir procédé à ce nettoyage, je dirais que le coût financier constitue en réalité la plus petite partie du problème.
Chaque fois qu'un nouvel ingénieur rejoint l'équipe et explore nos systèmes, il rencontre ces processus mystérieux. « Que fait cet ouvrier ? » devient une question qui ronge le temps d'intégration et crée de l'incertitude. Nous sommes tous passés par là, regardant un morceau de code, craignant d'y toucher parce que peut être c'est faire quelque chose d'important.
Même les infrastructures « oubliées » nécessitent parfois une attention particulière. Mises à jour de sécurité, modifications des dépendances, correctifs de compatibilité lorsque quelque chose d'autre change. Cela a conduit notre équipe à consacrer des cycles de maintenance à des chemins de code qui ne servaient à rien.
Et avec le temps, les connaissances institutionnelles s’estompent. Était-ce critique ? Était-ce une solution temporaire devenue permanente ? La personne qui l'a créé a quitté l'entreprise il y a des années et le contexte est parti avec lui.
Comment cela se produit-il ?
Il est facile de pointer du doigt, mais la vérité est que cela se produit naturellement dans tout système à longue durée de vie.
Une fonctionnalité devient obsolète, mais le travail en arrière-plan qui la prenait en charge continue de s'exécuter. Quelqu'un fait tourner un travailleur « temporairement » pour gérer une migration, et il n'est jamais démoli. Une tâche planifiée devient redondante après un changement d'architecture, mais personne ne pense à vérifier.
Nous avions l'habitude d'envoyer des e-mails de célébration d'anniversaire chez Buffer. Pour ce faire, nous avons exécuté une tâche planifiée qui vérifiait dans toute la base de données les anniversaires correspondant à la date actuelle et envoyait aux clients un e-mail personnalisé. Lors d'une refactorisation en 2020, nous avons changé notre outil de messagerie transactionnelle, mais avons oublié de supprimer ce travailleur : il a continué à fonctionner pendant encore cinq ans.
Il ne s’agit pas d’échecs individuels, mais plutôt d’échecs de processus. Sans un nettoyage intentionnel intégré à notre façon de travailler, l’entropie l’emporte.
Comment notre architecture nous a aidé à le trouver
Comme de nombreuses entreprises, Buffer a adopté le mouvement des microservices (une approche populaire selon laquelle les entreprises divisent leur code en plusieurs petits services indépendants) il y a des années.
Nous divisons notre monolithe en services distincts, chacun avec son propre référentiel, pipeline de déploiement et infrastructure. À l’époque, cela avait du sens : chaque service pouvait être déployé seul, avec des frontières claires entre les équipes.
Mais au fil des années, nous avons constaté que les frais généraux liés à la gestion de dizaines de référentiels dépassaient les avantages pour une équipe de notre taille. Nous avons donc regroupé dans un référentiel unique multiservice. Les services existent toujours comme des frontières logiques, mais ils vivent ensemble en un seul endroit.

C’est ce qui a rendu la découverte possible.
Dans le monde des microservices, chaque référentiel est son propre îlot. Un travailleur oublié dans un dépôt peut ne jamais être remarqué par les ingénieurs travaillant dans un autre. Il n'existe pas d'endroit unique pour rechercher les noms de files d'attente, ni de vue unifiée de ce qui s'exécute et où.
Avec tout dans un seul référentiel, nous pourrions enfin avoir une vue d’ensemble. Nous pourrions retracer chaque file d'attente jusqu'à ses consommateurs et producteurs. Nous avons pu repérer des files d'attente avec des producteurs mais pas de consommateurs. Nous avons pu trouver des travailleurs faisant référence à des files d'attente qui n'existaient plus.
La consolidation n’a pas été conçue pour nous aider à trouver une infrastructure zombie, mais elle a rendu cette découverte presque inévitable.
Ce que nous avons réellement fait
Une fois que nous avons identifié les processus orphelins, nous avons dû décider quoi en faire. Voici comment nous l’avons abordé.
Tout d’abord, nous avons retracé chacun jusqu’à son origine. Nous avons fouillé l'historique de Git et l'ancienne documentation pour comprendre pourquoi chaque travailleur a été créé en premier lieu. Dans la plupart des cas, l’objectif initial était clair : une migration de données ponctuelle, une fonctionnalité qui a pris fin, une solution de contournement temporaire qui a perdu son utilité.
Ensuite, nous avons confirmé qu’ils étaient réellement inutilisés. Avant de supprimer quoi que ce soit, nous avons ajouté une journalisation pour vérifier que ces processus n'effectuaient pas discrètement quelque chose d'important que nous avions manqué. Nous avons surveillé pendant quelques jours pour nous assurer qu’ils n’étaient pas appelés du tout, et nous les avons supprimés progressivement. Nous n'avons pas tout supprimé d'un coup. Nous avons supprimé les processus un par un, en surveillant tout effet secondaire inattendu. (Heureusement, il n'y en avait pas.)
Enfin, nous avons documenté ce que nous avons appris. Nous avons ajouté des notes à nos documents internes sur ce que chaque processus avait fait à l'origine et pourquoi il avait été supprimé, afin que les futurs ingénieurs ne se demandent pas si quelque chose d'important manquait.
Qu'est-ce qui a changé après le nettoyage
Nous n’en sommes encore qu’au début de la mesure de l’impact total, mais voici ce que nous avons vu jusqu’à présent.
Notre inventaire des infrastructures est désormais précis. Quand quelqu'un demande : « Quels travailleurs dirigeons-nous ? » nous pouvons réellement répondre à cette question en toute confiance.
Les conversations d’intégration sont également devenues plus simples. Les nouveaux ingénieurs ne tombent pas sur des processus mystérieux et ne se demandent pas s'ils manquent de contexte. La base de code reflète ce que nous faisons réellement, et non ce que nous faisions il y a cinq ans.
Traitez les refactors comme de l'archéologie et de la prévention
Ce que je retiens le plus de ce projet : chaque refactor important est une opportunité pour l’archéologie.
Lorsque vous êtes plongé dans un système et que vous comprenez vraiment comment les éléments s'articulent, vous êtes dans la position idéale pour vous demander ce qui est encore nécessaire. Cette file d'attente d'un vieux projet ? Le travailleur que quelqu'un a créé pour une migration de données ponctuelle ? La tâche planifiée qui fait référence à une fonctionnalité dont vous n'avez jamais entendu parler ? Ils sont peut-être encore en train de courir.
Voici ce que nous intégrons dans notre processus à l’avenir :
- Pendant toute refactorisationdemandez : qu'est-ce qui touche ce système d'autre que nous n'avons pas examiné depuis un moment ?
- Lors de la dépréciation d'une fonctionnalitésuivez-le jusqu'à ses processus d'arrière-plan, et pas seulement jusqu'au code destiné à l'utilisateur.
- Quand quelqu'un quitte l'équipedocumentez ce dont ils étaient responsables, en particulier ce qui s'exécute en arrière-plan.
Nous avons encore des parties plus anciennes de notre base de code qui n'ont pas encore été migrées vers le référentiel unique. Au fur et à mesure que nous poursuivons la consolidation, nous sommes convaincus que nous trouverons davantage de ces reliques cachées. Mais maintenant, nous sommes prêts à les attraper et à empêcher la formation de nouveaux.
Lorsque tout votre code réside au même endroit, l’infrastructure orpheline n’a nulle part où se cacher.