- Une bonne conception de système est une architecture qui ne paraît pas complexe et qui fonctionne longtemps sans problème particulier
- La partie la plus difficile de la conception de système est la gestion de l’état (state), et il est important de réduire autant que possible le nombre de composants qui stockent de l’état
- La base de données est généralement l’endroit où l’état est conservé ; il faut donc privilégier une approche centrée sur la conception du schéma et l’indexation, ainsi que sur la suppression des goulots d’étranglement
- Des éléments comme le caching, le traitement d’événements et les tâches en arrière-plan doivent être introduits avec prudence pour les performances et la maintenabilité, et il vaut mieux éviter d’en abuser
- Plutôt qu’une conception complexe, l’essentiel pour construire un système durable et stable est d’utiliser correctement des composants et des méthodes simples, suffisamment éprouvés
Définition de la conception de système et approche globale
- Si la conception logicielle consiste à assembler du code, la conception de système consiste à combiner différents services
- Les principaux composants d’une conception de système sont les serveurs applicatifs, bases de données, caches, files, bus d’événements, proxys, etc.
- Une bonne conception suscite des réactions comme « il n’y a pas de problème particulier », « ça s’est terminé plus facilement que prévu » ou « il n’y a pas besoin de se préoccuper de cette partie »
- À l’inverse, une conception complexe et tape-à-l’œil peut masquer un problème fondamental ou révéler une surconception
- Plutôt que d’introduire d’emblée un système complexe, il est préférable de partir d’une structure simple, minimale mais fonctionnelle, puis de la faire évoluer progressivement
Distinction entre état (state) et sans état (stateless)
- En conception logicielle, la partie la plus délicate est précisément la gestion de l’état
- Un service qui ne stocke pas d’information et renvoie immédiatement un résultat (comme le rendu PDF de GitHub) est stateless
- En revanche, un service qui écrit dans une base de données gère de l’état
- Il est préférable de réduire au maximum le nombre de composants qui stockent l’état dans le système. Cela diminue la complexité du système et le risque de panne
- Une architecture où une seule partie gère l’état, tandis que les autres services se concentrent sur des rôles stateless comme l’appel d’API ou l’émission d’événements, est recommandée
Conception de la base de données et points de goulot d’étranglement
Conception du schéma et des index
- Pour stocker les données, il faut une conception de schéma lisible par des humains
- Un schéma trop flexible (par exemple, tout stocker dans une colonne JSON) peut peser sur le code applicatif et sur les performances
- Il faut définir des index adaptés en fonction des colonnes qui seront fréquemment interrogées. Indexer absolument tout crée au contraire un surcoût inutile
Méthodes de résolution des goulots d’étranglement
- L’accès à la base de données constitue souvent un goulot d’étranglement lourd
- Quand c’est possible, il est plus performant de traiter les données complexes dans la base elle-même, via des JOIN par exemple, plutôt que dans l’application
- Lorsqu’on utilise un ORM, il faut faire attention à ne pas déclencher des requêtes dans une boucle
- Selon les besoins, on peut aussi scinder les requêtes afin de mieux gérer la charge sur la base ou la complexité des requêtes
- Une stratégie efficace consiste à répartir les requêtes de lecture sur des read replicas afin de réduire la charge du nœud principal d’écriture
- Lorsqu’un grand volume de requêtes arrive, les transactions et les écritures peuvent facilement surcharger la base de données ; il faut donc envisager le query throttling
Séparer les tâches lentes des tâches rapides
- Les opérations avec lesquelles l’utilisateur interagit doivent répondre en quelques centaines de millisecondes
- Pour les tâches longues (par exemple une conversion PDF volumineuse), il est efficace de fournir immédiatement le strict minimum côté front, puis de déléguer le reste en arrière-plan
- Les tâches en arrière-plan fonctionnent généralement avec une file (par exemple Redis) et un job runner
- Pour les tâches planifiées très en avance, il est souvent plus pratique de les gérer dans une table dédiée en base de données plutôt que dans Redis, puis de les exécuter via un scheduler
Caching
- Le caching permet de réduire les coûts et d’améliorer les performances lorsqu’on répète des opérations identiques ou coûteuses
- En général, les ingénieurs juniors qui découvrent le cache veulent tout mettre en cache, tandis que les ingénieurs plus expérimentés sont plus prudents sur son adoption
- Le cache introduit un nouvel état ; il existe donc des risques de synchronisation, d’erreurs et de données périmées
- Il est préférable d’essayer d’abord des optimisations comme l’ajout d’index sur les requêtes, puis d’appliquer le caching si nécessaire
- Pour des caches volumineux, on peut aussi utiliser une approche consistant à stocker périodiquement les données dans un stockage de documents comme S3 ou Azure Blob Storage, plutôt que dans Redis/Memcached
Traitement des événements
- La plupart des entreprises disposent d’un event hub (par exemple Kafka), et divers services y distribuent le traitement de manière orientée événements
- Plutôt que de multiplier les événements, une simple conception d’API en requête-réponse est plus utile pour la journalisation et la résolution de problèmes
- Le traitement orienté événements est adapté lorsque l’émetteur n’a pas à se soucier du comportement du récepteur, ou dans des scénarios à fort volume et tolérants à la latence
Modes de transfert des données : push et pull
- Pour le transfert de données, il existe deux approches : Pull (requête puis réponse) et Push (transmission automatique lors d’un changement)
- L’approche Pull est simple, mais elle peut entraîner des requêtes répétées et une surcharge
- Avec l’approche Push, le serveur transmet immédiatement les changements au client ; elle est donc plus efficace et plus adaptée au maintien de données à jour
- Pour gérer un grand nombre de clients, il faut faire évoluer l’infrastructure selon l’approche choisie (file d’événements, plusieurs serveurs de cache, etc.)
Se concentrer sur les hot paths
- Les hot paths désignent les chemins les plus importants du système, là où circule le plus de données
- Les hot paths offrent peu d’alternatives et, si leur conception échoue, ils peuvent provoquer de graves problèmes pour l’ensemble du service ; une conception soignée est donc indispensable
- Plutôt que de disperser les ressources sur des fonctionnalités mineures aux nombreuses options, il est plus efficace de concentrer la conception et les tests sur les hot paths
Journalisation, métriques et traçage
- En cas d’incident, il faut consigner activement des logs détaillés sur les chemins anormaux (unhappy path) afin de faciliter le diagnostic
- Il est nécessaire de collecter des indicateurs d’observabilité de base comme les ressources système (CPU/mémoire), la taille des files, ou les temps de requête et de traitement
- Au lieu de ne regarder que la moyenne, il faut absolument observer aussi des indicateurs de distribution comme les latences p95 et p99. Une petite fraction des requêtes les plus lentes peut concerner les utilisateurs les plus importants
Kill switch, retries et reprise après incident
- L’usage stratégique d’un kill switch (blocage temporaire du système) et des retries est important
- Retenter sans discernement ne fait qu’alourdir les autres services ; il est plus efficace de contrôler les requêtes en amont avec un circuit breaker, par exemple
- L’introduction d’une Idempotency Key permet d’éviter les opérations en double lors du retraitement d’une même requête
- Dans certaines situations de panne, il faut choisir entre fail open et fail closed. Par exemple, pour le rate limiting, un fail open (autoriser) a moins d’impact sur l’utilisateur. En revanche, pour l’authentification, le fail closed est indispensable
Conclusion
- Certains sujets comme la séparation des services, les conteneurs, l’introduction de VM ou le tracing ont été laissés de côté, mais utiliser des composants bien éprouvés au bon endroit reste, sur le long terme, la manière la plus stable de construire un système
- Les conceptions techniquement « spéciales » sont en réalité très rares, et une conception simple au point d’en être ennuyeuse est au contraire celle qu’on rencontre le plus souvent en pratique
- Fondamentalement, une bonne conception de système consiste à combiner de manière sûre des méthodes suffisamment éprouvées, sans chercher à attirer l’attention
Aucun commentaire pour le moment.