Topic

Comment concevoir une plateforme web scalable

Insights/ Architecture web & plateformes / Décisions d'architecture

12 janv. 2024 - 11 min de lecture

Comment concevoir une plateforme web scalable
Écouter l’article00:00 / 13:37

Ce que « scalable » veut vraiment dire

« Scalable » est utilisé comme synonyme de « bon », ce qui rend le mot presque inutile. Pour une plateforme web, la scalabilité est quelque chose de plus étroit et de plus mesurable : le système peut absorber plus de ce qu’on lui demande d’absorber (plus de requêtes, plus d’utilisateurs, plus de données, plus d’écritures concurrentes) sans que l’utilisateur le remarque, sans que l’équipe coure derrière le feu, et sans avoir à réécrire l’architecture depuis zéro.

Cette définition est opérationnelle, pas théorique. Une plateforme est scalable quand l’équipe sait ce qui casse en premier sous charge, qu’elle a déjà déplacé ou supprimé ce goulot d’étranglement une ou deux fois, et qu’elle a un plan pour le suivant. Une plateforme n’est pas scalable quand la réponse à « que se passe-t-il à trois fois la charge actuelle » est « on verra ». Scaler, ce n’est pas faire plus gros ; c’est savoir ce qui casse en premier et déplacer le goulot d’étranglement avant que l’utilisateur ne le voie.

Cet article est la couche opérationnelle de la conversation d’architecture. Le contexte d’ensemble pour les choix ci-dessous est dans l’article sur le choix d’architecture ; ici, l’angle est ce qui fait qu’une architecture déjà choisie tient quand la charge augmente.

Partir de la forme de la charge, pas de l’architecture

L’artefact de planification de capacité le plus utile n’est pas un chiffre cible (« nous devons supporter un million d’utilisateurs »), c’est une forme de charge : comment le trafic réel se distribue dans le temps, dans l’espace et par type de requête. Toute conception scalable en découle.

Trois propriétés comptent plus que n’importe quel chiffre de pic. La première, c’est le ratio pic-sur-moyenne. Un site qui traite mille requêtes par seconde en moyenne et dix mille au pic est un problème de conception différent d’un site qui en traite deux mille en moyenne et trois mille au pic. Le premier a besoin d’une architecture qui absorbe un pic de 10x ; le second a besoin de tourner chaud et stable. Faire comme s’il s’agissait du même problème est la manière dont les systèmes sont sur-ingéniérés pour des pics qui n’arrivent pas, ou sous-ingéniérés pour des pics qui arrivent.

La deuxième, c’est le mix de requêtes. Un site à 90 % lecture et 10 % écriture se résout avec du cache. Un site à 50/50 se résout avec du tuning de base de données. Un site à 10 % lecture et 90 % écriture se résout avec des files, des batchs et des écritures asynchrones. Connaître le mix, c’est ce qui dit quel levier tirer en premier.

La troisième, c’est la distribution géographique. Une plateforme nationale avec des utilisateurs sur un seul fuseau horaire a des choix de cache et de CDN différents d’une plateforme globale avec un trafic réparti sur plusieurs continents. La stratégie CDN qui marche pour l’une est du gaspillage pour l’autre.

Une planification de capacité qui part de ces trois propriétés produit systématiquement des architectures qui tiennent. Une planification qui part d’un nombre cible d’utilisateurs produit systématiquement des architectures fausses d’un ordre de grandeur dans un sens ou dans l’autre.

La hiérarchie de cache qui fait l’essentiel du travail

L’essentiel de la scalabilité en production vient du cache, et l’essentiel des erreurs de cache vient de cacher au mauvais endroit. Une plateforme utile a des caches à quatre niveaux, chacun avec un rôle précis.

Cache navigateur : le navigateur de l’utilisateur garde les assets statiques (images, scripts, feuilles de style) et les GET conditionnels. Régler les en-têtes de cache correctement est le gain de scalabilité le moins cher disponible, et la plupart des sites s’y trompent en cachant trop agressivement (contenu périmé pendant des heures) ou pas du tout (chaque navigation re-télécharge tout).

Cache CDN : un réseau de distribution de contenu garde des pages HTML entières et des assets près de l’utilisateur. Pour des pages statiques ou peu changeantes, cela transforme la majeure partie du trafic en un problème que le CDN résout avant que la requête n’atteigne l’application. Pour des pages dynamiques, le CDN peut quand même garder pour des fenêtres courtes (secondes à minutes) et absorber des pics de trafic qui détruiraient autrement l’origine.

Cache applicatif : un cache en mémoire ou partagé (Redis, Memcached, ou un LRU en processus) garde les résultats de calculs coûteux et de requêtes en base. La discipline ici, c’est de cacher la bonne chose à la bonne clé avec le bon TTL : trop court, le cache ne fait rien ; trop long, les utilisateurs voient des données périmées. L’invalidation de cache est le deuxième des deux fameux « problèmes difficiles » ; le premier, c’est nommer les choses, mais l’invalidation de cache est plus difficile.

Cache de base de données et cache de plan de requête : la base de données elle-même garde les pages fréquemment lues en mémoire. Dimensionner correctement le serveur de base pour que le working set tienne en RAM est souvent un levier plus important que toute optimisation côté application, parce qu’une requête qui touche la mémoire est plusieurs ordres de grandeur plus rapide qu’une requête qui touche le disque.

Quand les quatre couches travaillent ensemble, le serveur applicatif ne voit que les requêtes qu’il doit réellement traiter, pas celles que le cache aurait pu absorber. Quand l’une est manquante ou mal configurée, ajouter des instances de l’application revient à résoudre le mauvais problème.

La base de données : là où la plupart des plateformes cassent réellement

Pour la plupart des plateformes web en croissance, la base de données est la première chose à casser sous charge soutenue, et la dernière chose à laquelle l’équipe pense au moment de la planification. Cinq choses bougent dans le bon ordre pour une plateforme qui passe à l’échelle.

Les index. Le travail de performance à plus fort effet de levier, et le plus souvent oublié. Des requêtes lentes deviennent rapides parce que le bon index existe ; sans lui, aucune scalabilité horizontale n’aide.

Le pooling de connexions. L’application ouvre des connexions vers la base. Sans pool, chaque requête ouvre une nouvelle connexion, ce qui est coûteux et fragile. Avec un pool, l’application réutilise les connexions et le nombre de connexions de la base reste sous contrôle. C’est un changement de 20 lignes qui évite la plupart des incidents « la base est à 100 % ».

Les répliques de lecture. La plupart des plateformes web sont à dominante de lecture. Router les lectures vers des répliques pendant que les écritures vont vers la primaire multiplie la capacité de lecture du système sans changer significativement l’application. Le piège honnête, c’est le retard de réplication : une lecture qui vient juste d’être faite sur la primaire peut ne pas encore être visible sur la réplique, ce qui peut produire des bugs surprenants dans des workflows qui lisent juste après l’écriture.

Le sharding par tenant ou par clé de domaine. Quand une seule primaire ne peut plus tenir la charge d’écriture, les données sont réparties sur plusieurs bases selon une clé que l’application sait router. C’est de l’ingénierie lourde avec de vrais modes de panne ; c’est la bonne réponse quand les répliques de lecture ne suffisent plus, et la mauvaise réponse plus tôt que ça.

Le cache à la frontière de la base. Beaucoup de problèmes « la base est le goulot » sont en réalité des problèmes « nous posons la même question à la base 50 fois par seconde ». Un cache applicatif devant le chemin de lecture absorbe cela et libère la base pour les requêtes qui en ont réellement besoin.

Ces évolutions ne sont pas des microservices ; c’est l’échelle ennuyeuse et bien connue que toute plateforme en croissance gravit. Sauter des barreaux, c’est la manière dont les équipes découvrent, par la voie coûteuse, pourquoi les barreaux sont là.

Les frontières asynchrones et l’architecture de files

Une plateforme scalable sépare agressivement le travail « l’utilisateur attend » du travail « l’utilisateur n’attend pas ». Le premier doit être rapide ; le second peut être lent du moment qu’il est fiable.

Les requêtes face à l’utilisateur font le minimum possible de travail de manière synchrone : valider, persister un petit enregistrement, mettre un job en file, répondre. Des workers en arrière-plan reprennent le job et font le travail lourd (envoi d’e-mails, génération de rapports, appels à des APIs tierces, traitement d’uploads, recalcul d’agrégats). La file entre les deux absorbe les pics : cent jobs en attente ne ralentissent pas l’utilisateur, et les workers traitent à leur propre rythme.

Trois propriétés de file font que cela marche en pratique. L’idempotence : un job doit pouvoir être exécuté deux fois sans dégât, parce que des retries vont arriver. Le backoff et les dead-letter queues : quand un job échoue à répétition, il cesse de bloquer la file et atterrit quelque part qu’un humain peut examiner. La visibilité : la profondeur de file, le débit des workers, le nombre de jobs échoués font partie du tableau de bord, pas d’une boîte noire.

Les plateformes qui tiennent sous les pics sont celles où le chemin synchrone reste sous contrôle parce que la majeure partie du travail se passe en asynchrone, la file jouant le rôle d’amortisseur. Les plateformes qui plient sous les pics sont celles où chaque requête déclenche tout en ligne, et où un ralentissement d’une API tierce fait tomber tout le site.

Observabilité sous charge : le système d’alerte précoce

Une plateforme scalable n’est pas une plateforme qui ne casse jamais ; c’est une plateforme qui signale qu’elle est sur le point de casser, à temps pour que l’équipe fasse quelque chose. Cela exige une observabilité conçue pour la charge, pas seulement pour le débogage.

Trois choses doivent être visibles en permanence. Les percentiles de latence, pas les moyennes. Le p99 du temps de réponse vous dit ce que voient vos pires utilisateurs ; la moyenne ne vous dit presque rien. Un site dont le p50 est à 200 ms et le p99 à 8 secondes a un problème qu’une moyenne de 400 ms cache.

Les indicateurs de saturation. Utilisation CPU, pression mémoire, nombre de connexions à la base, profondeur de file, taux de hit du cache. Ils disent à quel point le système est proche de ses limites, pas seulement comment il se comporte aujourd’hui. Un système à 80 % de saturation aujourd’hui est à une mauvaise release d’un mauvais après-midi.

Les ventilations par route ou par tenant. Le p99 agrégé peut être bon pendant qu’un endpoint précis ou un client précis est en souffrance. Les ventilations le font remonter avant que cela ne devienne une avalanche de tickets de support.

La discipline qui rend l’observabilité utile, c’est d’alerter sur la saturation et la latence avant la panne visible par l’utilisateur, avec un seuil réglé par l’expérience plutôt que copié d’un tutoriel. Les équipes qui réussissent ce point voient les incidents arriver. Les équipes qui se trompent apprennent les incidents par le support client.

Conclusion

Une plateforme web scalable, ce n’est pas celle qui a les serveurs les plus gros ; c’est celle dont l’architecture, le cache, la base, le chemin asynchrone et l’observabilité tiennent tous sous une charge supérieure à la charge actuelle, avec une marge délibérée. Scaler, c’est déplacer le goulot d’étranglement : chaque goulot résolu en révèle un suivant, et les plateformes qui passent bien à l’échelle sont celles dont les équipes s’y attendent et ont l’habitude de déplacer le prochain goulot avant qu’il ne devienne un incident. Construire pour un hypothétique million d’utilisateurs est généralement une erreur ; construire pour la prochaine étape plausible, avec les outils opérationnels pour voir l’étape d’après venir, est généralement le bon mouvement.

Le contexte plus large, dont la manière dont la scalabilité se relie au reste de la décision d’architecture, est rassemblé dans le cluster architecture web et plateformes. Et lorsque la question passe de « la plateforme est-elle scalable » à « nous avons une vraie forme de charge, une vraie base sous pression, et il nous faut quelqu’un pour concevoir les index, le cache, les répliques de lecture et le chemin asynchrone », c’est exactement ce sur quoi mon accompagnement architecture de base de données et performance est centré.

- Haja Faniry

Services liés

Développement d'applications web

Développement d'applications web sur mesure pour entreprises, startups et organisations internationales.

Plateformes Numériques pour ONG & Organisations

Développement de plateformes numériques et systèmes de données pour ONG, institutions de recherche et organisations internationales.

Développement d’API & Intégration de Systèmes

Développement d’API et intégration de systèmes permettant de connecter les plateformes numériques et d’automatiser les échanges de données.

Article précédent
Les erreurs d’architecture courantes dans les projets web
Article suivant
Quand une architecture headless a du sens
Comment concevoir une plateforme web scalable | Haja Faniry