- En reconstruisant un système de comparaison de plans d’assurance santé Medicare aux États-Unis, l’auteur partage son expérience d’une architecture simple composée uniquement de technologies éprouvées (Postgres, golang, React, etc.), capable de traiter de façon stable plus d’un milliard de requêtes web
- L’architecture a été conçue avec pour objectif la simplicité et la stabilité, atteignant un temps de réponse moyen inférieur à 10 ms et un taux d’incident très faible
- L’innovation (
innovation token) n’a été utilisée qu’au strict minimum, uniquement pour les séparations structurelles essentielles (3 grands modules, communication gRPC), tandis que tout le reste reposait sur des méthodes ennuyeuses mais fiables
- Jusqu’à la gestion du schéma de base de données, le pipeline ETL, les tests, les logs, la documentation et les outils CLI, tous les éléments d’exploitation ont été construits de manière simple et reproductible pour aboutir à un système que toute l’équipe peut facilement comprendre et maintenir
- C’est un exemple très concret montrant que le contrôle qualité continu et une forte cohésion d’équipe fonctionnent aussi sur des projets publics à grande échelle
Serving a billion web requests with boring code
High level
- Pendant deux ans et demi, l’auteur a dirigé le développement d’un site web gouvernemental américain de comparaison et d’achat de plans Medicare
- Le système traite 5 millions de requêtes API par jour en moyenne, avec un temps de réponse moyen inférieur à 10 ms, et 95 % des requêtes sous les 100 ms
- Le taux d’incident était très faible, au point que les cas où un ingénieur a réellement dû être réveillé en pleine nuit se comptaient sur les doigts d’une main
- L’ensemble reposait uniquement sur des technologies éprouvées et compréhensibles par tous, comme Postgres, golang et React, pour construire un système régulièrement stable
Boring über alles
- Le principe prioritaire était de toujours privilégier des technologies “ennuyeuses” et éprouvées (Choose Boring Technology)
- Les tentatives d’innovation n’étaient engagées qu’aux endroits indispensables, en économisant soigneusement les innovation tokens
- Les solutions complexes et tape-à-l’œil étaient moins recherchées que des technologies et processus stables et clairs
The boring bits
- Postgres : cœur du stockage de données, offrant à la fois fiabilité et capacité de montée en charge. Même les recherches complexes, comme la recherche à facettes, ont été résolues avec Postgres
- golang : builds et déploiements rapides, artefacts binaires explicites. La gestion des erreurs est intuitive, et les nouveaux membres de l’équipe peuvent s’y adapter facilement
- React : le framework SPA le plus éprouvé parmi les options envisagées, déjà bien maîtrisé par l’équipe. L’accessibilité et le support de divers appareils ont aussi été des critères importants
- À long terme, des problèmes de taille de bundle et de baisse de performance sont apparus, mais dans le contexte de l’époque, c’était le meilleur choix pour livrer à temps
The innovation tokens
- Backend modulaire : au lieu d’un backend en microservices ou d’un monolithe, l’ensemble a été structuré en 3 grands modules (
druginfo, planinfo, beneinfo)
- Chaque module utilise sa propre base Postgres, et le partage de données ne se fait qu’au travers de gRPC
druginfo : indexe de manière très fine des informations sur les prix des médicaments, où les combinaisons pharmacies/assurances/conditionnements croissent de manière exponentielle, ce qui impose un prétraitement complexe et de fortes optimisations de performance
planinfo : reçoit chaque jour de nouvelles données CMS et reconstruit intégralement la base de données à chaque fois pour préserver l’immuabilité
beneinfo : seule partie qui stocke les informations réelles des assurés, avec un minimum de PII (données personnelles identifiables) sensibles conservées. La conception et l’exploitation ont été pensées pour réduire au maximum le risque de fuite de données
- gRPC : avantage de définir clairement en code les interfaces de communication entre modules. Très bonne intégration avec les outils d’automatisation
- En contrepartie, les builds, l’outillage et le débogage étaient plus complexes, et moins intuitifs qu’une API JSON
- Via
grpc-gateway, le système a pris en charge le client web et absorbé sans difficulté un trafic important
Strict backwards compatibility
- Le maintien de la rétrocompatibilité des API et de la base de données a été appliqué de manière stricte
- Les champs de l’API publique ne sont jamais supprimés et sont conservés indéfiniment, sauf en cas de problème de sécurité
- Pour les colonnes de base de données, les ajouts sont libres, mais les suppressions suivent plusieurs étapes de validation (suppression des références → attente de quelques semaines → suppression réelle)
- Cette discipline a constitué le socle essentiel d’un rythme de changement élevé avec des déploiements et une exploitation stables
Faceted search
- Implémentation de la recherche à facettes uniquement avec Postgres, sans ElasticSearch
- Toute la logique de recherche tient dans une seule fonction d’environ 250 lignes qui combine des conditions sur une table de plans bien indexée
- L’accent a été mis sur les besoins métier, en résolvant le problème simplement et sans complexité inutile
Database
-
creation
- Le schéma de base de données est géré via des fichiers
.sql numérotés, chargés dans l’ordre pour garantir la fiabilité
- Les bases
planinfo et beneinfo sont recréées chaque jour, donc aucune migration n’est nécessaire. En cas d’erreur de configuration, comme une incompatibilité de version, l’application est conçue pour ne pas démarrer du tout
-
ETL
- Des scripts shell propres à chaque source chargent les données dans S3, puis
cron fait récupérer par une instance EC2 le dernier code ETL et les dernières données pour créer une nouvelle base RDS
- Utilisation intensive de l’instruction COPY de Postgres pour charger efficacement de gros volumes de données au lieu de
INSERT
- Il faut 2 à 4 heures par jour pour basculer des centaines de millions de lignes vers une nouvelle base
-
models
- La bibliothèque xo sert à générer automatiquement les modèles de base de données, avec des templates personnalisés adaptés à l’équipe
-
testing
- La plus grosse erreur a été de trop investir dans des tests fondés sur
sqlmock, ce qui est devenu très pénible à maintenir dans un contexte où les données changent fréquemment
- Avec une vraie base immuable, des tests sur une base réelle auraient été plus efficaces
-
Local database for development
- Grâce à des scripts générant automatiquement un sous-ensemble de données pour chaque table, chaque développeur peut travailler et tester sur une petite base locale issue des données réelles
- Mettre ce type d’outil en place avant que la base ne grossisse maximise fortement l’efficacité de développement de toute l’équipe
Miscellaneous tooling
- Pour l’automatisation des opérations et de l’observabilité, des outils CLI ont été développés en scripts shell, en regroupant toutes les fonctions utilitaires dans un ensemble unique
- L’équipe a aussi activement créé et utilisé des outils de terrain, comme la visualisation immédiate en graphique de logs Splunk via des commandes Slack
Logging
- Un request id est généré à l’entrée de chaque requête, puis attaché à tous les contextes de logs afin de permettre un suivi partout
- zerolog a été utilisé pour concevoir un système de logs sûr et structuré
- Des logs indispensables sont enregistrés à tous les moments importants : entrée et sortie du système, situations d’exception, etc.
Documentation
- La documentation Markdown sur GitHub est convertie avec sphinx-book-theme pour être exploitée sous forme de wikibook
- Tous les membres de l’équipe contribuent activement à la documentation, afin que l’ensemble des documents système soit retrouvable au même endroit
- Une excellente culture de la documentation a fortement amélioré la croissance de l’équipe, la maintenance et l’efficacité de l’onboarding des nouveaux arrivants
Runtime integrations
- Les demandes des clients susceptibles de dégrader les performances, comme l’insertion de scripts d’analyse, ont été réduites autant que possible par la persuasion
- Les traitements de requêtes ont aussi été déplacés de l’exécution dans le navigateur vers le build time afin de préserver les performances du service
- En pratique, toutes les demandes clients n’ont pas pu être bloquées, et certaines ont effectivement entraîné une baisse de performance
And more
- Au-delà de la technique, l’auteur souligne que l’ambiance positive et collaborative de l’équipe ainsi qu’une forte motivation ont été les véritables moteurs du succès de ce système à grande échelle
- C’est un cas concret qui montre la force de choix opérationnels modestes mais importants, ainsi que d’une gestion de la qualité constante
18 commentaires
On a fait ça d'une banalité pendant 2,5 ans ?!
Je me demandais ce qu’était la recherche à facettes, alors j’ai suivi le lien et il y a d’autres lectures intéressantes.
https://www.cybertec-postgresql.com/en/faceting-large-result-sets/
https://roaringbitmap.org/about/
https://github.com/cybertec-postgresql/pgfaceting
Les réactions autour de « ennuyeux » sont amusantes haha. Si on voulait le remplacer par un autre mot, qu’est-ce qui irait bien ? Banal, courant ?
Traduire
boringpar « ennuyeux » ne rend pas du tout son sens d’origine. La boringness fait partie de la philosophie de conception de Go.L’air de rien…
En Corée, tout finit toujours en Java-land, alors ça leur paraît étrange, haha.
Je pense que golang et React sont tous deux des langages de programmation d’entreprise « boring » de la nouvelle ère.
Comme
boringne se traduit pas à 100 % correctement par « ennuyeux », on dirait que la nuance n’est pas vraiment transmise aux lecteurs coréens.J’aimerais vivre dans le monde ennuyeux de Postgres, golang et React.
Oui, j’ai moi aussi cru à une blague en voyant le titre.
À l’étranger, cela semble être considéré comme une stack ennuyeuse.
En réalité, Go est tout simplement le choix le plus facile pour créer un serveur web...
J’ai l’impression qu’ils disent qu’il faut développer avec Rust, ou des langages du côté de la FP, pour que ce ne soit pas ennuyeux.
Des évidences tellement banales... des points importants qu’on néglige justement parce qu’ils paraissent trop évidents...
Cette stack ne me semble pas si ennuyeuse que ça. Si c’était vraiment ennuyeux, il faudrait au moins voir du Java en version 1.8 ou antérieure, ou du VB... c’est la pensée un peu irrévérencieuse qui me vient.
>Ce qui est bien avec le caractère banal de ces choses (si contraignant), c’est que leurs capacités sont bien comprises. Mais surtout, leurs modes de défaillance sont bien connus.
Il y a dans le texte original un lien lié à
boring, et à en juger par le contenu,boringsemble ici signifier « trop familier ».Il existe des termes plus appropriés comme experienced, verified ou skillful, donc le fait d’avoir utilisé boring à tout prix donne l’impression qu’il y avait une intention de faire du buzz.
Ce n’est pas ennuyeux à écrire, mais plutôt une pile « gukbap » devenue ennuyeuse à force d’avoir été trop utilisée, non ?
Jusqu’au noyau Linux 2.6.29 environ...
Le simple fait qu’ils aient utilisé gRPC… haha
Moi aussi, ma première réaction a été de me dire : quoi, Go serait ennuyeux ?
À la limite, on pourrait peut-être dire ça de classic asp.