English· Español· Deutsch· Nederlands· Français· 日本語· ქართული· 繁體中文· 简体中文· Português· Русский· العربية· हिन्दी· Italiano· 한국어· Polski· Svenska· Türkçe· Українська· Tiếng Việt· Bahasa Indonesia

un

invité
1 / ?
retour aux leçons

Deux façons de supporter plus de charge

Bienvenue

Lorsqu'un service commence à flancher sous la charge, l'opérateur est confronté à un choix. Rendre la machine existante plus puissante (plus de CPU, plus de RAM, des disques plus rapides). Ou ajouter plus de machines qui chacune effectue le même travail.

La première voie est appelée mise à l'échelle verticale (scale up). La seconde est appelée mise à l'échelle horizontale (scale out).

Cette leçon explique pourquoi presque toutes les architectures web modernes optent pour la mise à l'échelle horizontale et quelle propriété de la charge de travail rend ce choix viable. La réponse se cache dans un seul mot : état.

À la fin, vous comprendrez :

- Les courbes de coût de la mise à l'échelle verticale par rapport à l'horizontale & où chacune est pertinente

- Ce que signifient « stateful » (avec état) et « stateless » (sans état) en pratique, & pourquoi l'un d'eux se multiplie à moindre coût

- Le calcul qui dimensionne une flotte de répliques sous charge attendue et de pointe

- La règle de marge qui empêche une couche de s'effondrer au-delà du point d'inflexion de la file d'attente

- Où l'état doit résider (il ne disparaît jamais) & comment le sortir des couches qui doivent être mises à l'échelle

Pourquoi l'horizontal gagne au-delà d'un seuil

Mise à l'échelle verticale : une boîte plus grande

Avantages : simple. Aucune modification de code. Aucune coordination. Le même processus dispose désormais de plus de CPU.

Inconvénients : plafond. La plus grande VM disponible commercialement a une RAM et un nombre de cœurs finis. Au-delà, aucun argent n'achète plus de marge. Les coûts deviennent superlinéaires au-delà du point optimal de l'offre d'un fournisseur. Une défaillance de cette seule machine met le service entier hors service.

Mise à l'échelle horizontale : de nombreuses petites machines

Avantages : pas de plafond (jusqu'à votre volonté de payer et de coordonner les machines). La capacité augmente linéairement avec le nombre de répliques, de manière prévisible. Une défaillance d'une seule réplique supprime 1/N de la capacité, et non 100 %.

Inconvénients : nécessite que la charge de travail le supporte. Certaines charges de travail (une seule grande base de données, un serveur de jeu à état qui détient des sessions en direct) résistent à la mise à l'échelle horizontale. La coordination et la répartition de la charge deviennent des préoccupations opérationnelles.

Le point de bascule : tout service de production qui doit survivre à la défaillance d'une seule machine doit fonctionner sur au moins deux machines. Une fois que vous acceptez deux, vous avez déjà choisi la mise à l'échelle horizontale. À partir de là, la question n'est plus « devrions-nous ? » mais « à quel prix pouvons-nous ajouter la réplique suivante ? »

Le principal facilitateur : une charge de travail qui ne conserve aucun état par requête sur la machine elle-même. Alors, n'importe quelle réplique peut répondre à n'importe quelle requête, et l'ajout d'une réplique ajoute de la capacité sans coordination.

Mise à l'échelle verticale vs horizontale : courbe de coût et plafond

Une équipe exécute un service sur une seule VM avec 8 CPU, gérant 800 requêtes par seconde à 60 % de charge CPU. Ils s'attendent à ce que le trafic augmente de 4 fois au cours de l'année à venir. Ils débattent : louer une VM de 32 CPU (vertical) ou exécuter quatre VM identiques de 8 CPU derrière un équilibreur de charge (horizontal). Quelle option recommandez-vous, et citez deux raisons qui vont au-delà de la capacité brute.

Étatful vs Stateless en pratique

L'état ne disparaît jamais, il se déplace simplement

Composant avec état : conserve des informations dont la perte modifierait le comportement. Une base de données contenant des comptes utilisateurs. Un cache contenant des jetons de session. Un travailleur qui attache une connexion de streaming longue durée à un utilisateur spécifique.

Composant sans état : ne conserve aucune information dont la perte aurait de l'importance. Une couche web qui lit une requête, interroge une base de données et écrit une réponse. Chaque requête est autonome ; la couche ne retient rien entre les requêtes.

Insight clé : l'état ne disparaît jamais d'un système. Il se déplace vers une couche conçue pour le conserver (une base de données, un cluster Redis, un stockage d'objets). Les couches qui font face au trafic peuvent alors être sans état, et les couches sans état se dimensionnent horizontalement car n'importe quelle réplique peut répondre à n'importe quelle requête.

Test pratique : si vous tuez aléatoirement un processus de cette couche et le redémarrez, un utilisateur subirait-il une réponse erronée ou la perte d'une session ? Si oui, il détient de l'état. Si non, il n'en détient pas.

Exemples

- Un processus web Python qui lit des requêtes, interroge Postgres et renvoie du JSON : sans état. L'état réside dans Postgres.

- Un processus web Python qui conserve les paniers d'achat des utilisateurs en mémoire locale : avec état. Tuer le processus entraîne la perte des paniers.

- Un serveur WebSocket qui maintient des connexions ouvertes avec les utilisateurs de la messagerie : étatful au sens de la connexion. Tuer le processus rompt les connexions ; les clients doivent se reconnecter. Souvent, ces systèmes peuvent encore être mis à l'échelle horizontalement avec précaution (sessions collantes, hachage cohérent).

- Un cache Redis devant Postgres : étatful pour le contenu du cache, mais acceptable si les manques de cache sont tolérables. Une défaillance d'une réplique signifie un manque de cache, et non une perte de données.

Concevoir pour la mise à l'échelle horizontale = déplacer l'état hors de la couche qui doit être mise à l'échelle.

Auditer un niveau suspect

Une équipe exécute une API de recommandation sur 6 VM back-end derrière un proxy inverse. L'application : lit un identifiant d'utilisateur depuis la requête, récupère l'activité récente de l'utilisateur depuis Postgres, exécute un algorithme de notation, et renvoie une liste d'éléments recommandés. Deux comportements non standard :

- L'application maintient un cache « activité récente de l'utilisateur » en mémoire de processus, peuplé lors de la première requête pour un utilisateur, et réutilisé lors des requêtes suivantes.

- L'application utilise des sessions collantes : une fois qu'un utilisateur accède à la VM n°3, toutes ses requêtes suivantes sont dirigées vers la VM n°3 (le proxy est configuré pour un routage collant basé sur un cookie).

Identifiez lequel de ces deux comportements rend le niveau étatful et expliquez ce qui casserait si l'équipe tentait de passer de 6 VM à 12. Puis proposez une refonte qui permet au niveau de mettre à l'échelle horizontalement sans perdre l'avantage du cache utilisateur.

La formule des répliques

La formule de capacité la plus simple

Une fois qu'un niveau devient sans état, son dimensionnement devient une question d'arithmétique. Il faut suffisamment de répliques pour que la charge en régime permanent arrive et parte au même taux, avec une marge pour les pics de charge.

La formule :

répliques = ⌈ (charge_maximale × facteur_de_pics) / capacité_par_réplique ⌉ + marge

Où :

- charge_maximale : nombre maximal de requêtes par seconde que vous prévoyez en fonctionnement normal

- facteur_de_pics : un multiplicateur couvrant les courtes rafales au-dessus du pic (généralement 1,5 à 2 pour le trafic prévisible, 3 ou plus pour le viral / imprévisible)

- capacité_par_réplique : requêtes par seconde qu'une réplique gère avec une latence et une utilisation acceptables (généralement mesurées à 70 % de CPU, et non à saturation)

- marge : répliques supplémentaires pour que quelques pannes de répliques ne fassent pas s'effondrer le niveau (généralement 1 à 2 répliques pour les petites flottes, 10 à 20 % pour les plus grandes)

Exemple résolu : un backend gère 100 req/s à 70 % de CPU par réplique. La charge de pointe est de 600 req/s. Vous prévoyez des pics occasionnels de 2x. Vous souhaitez survivre à la défaillance de 2 répliques.

répliques = ⌈ (600 × 2) / 100 ⌉ + 2 = 12 + 2 = 14 répliques

La règle des 80 %

La capacité par réplique n'est pas le point de saturation. Mesurez la capacité à 70-80 % de CPU, jamais à 100 %.

Au-delà d'environ 80 % d'utilisation, les courbes de mise en file d'attente s'élèvent brusquement : une file qui fonctionnait en 10 ms à 60 % d'utilisation fonctionne en 80 ms à 90 % d'utilisation. C'est la latence, et non le débit, qui cède en premier. (La leçon associée geometry_of_stateless_horizontal_scaling dérive cette courbe mathématiquement.)

Mise à l'échelle automatique (Autoscaling) vs Provisionnement statique

Statique : provisionner pour la charge de pointe × la marge de sécurité et accepter le coût d'une faible utilisation en dehors des heures de pointe.

Mise à l'échelle automatique : un contrôleur ajoute et supprime des répliques en fonction de l'utilisation observée, de la latence cible ou de la profondeur de la file d'attente.

Avertissement sur le redimensionnement automatique : le temps de démarrage à froid est crucial. Si un nouveau réplique met 2 minutes à démarrer, le redimensionnement automatique ne peut pas répondre à une augmentation de charge de 30 secondes. Un système de redimensionnement automatique mature maintient un pool de répliques pré-provisionnées « chaudes » juste en dessous du seuil de montée en charge.

Formule de dimensionnement des répliques avec exemple chiffré

Dimensionner une flotte pour un nouveau service

Votre équipe prévoit de lancer une API de métadonnées vidéo. Les benchmarks montrent qu'une seule réplique gère 250 req/s à 70 % de CPU et avec une latence p99 de 50 ms. Le marketing prévoit une charge de pointe de 4 000 req/s pendant les heures de forte écoute. Un événement promotionnel planifié pourrait provoquer une augmentation temporaire de 3 fois la charge de pointe. Vous souhaitez que le service survive à 3 pannes simultanées de répliques sans dépasser 80 % d'utilisation sur les répliques restantes.

Appliquez la formule des répliques pour dimensionner la flotte de lancement. Montrez vos calculs étape par étape, puis expliquez une raison pour laquelle votre nombre pourrait tout de même être incorrect (démarrage à froid, réchauffage du cache, latence des dépendances, ou tout autre argument défendable).

Démarrage à froid, vidage lent & autres limites réelles

Les flottes réelles ont des limites réelles

La formule suppose que les répliques apparaissent instantanément, acceptent le trafic instantanément et libèrent le trafic instantanément. Aucune de ces hypothèses n'est valable en production.

Démarrage à froid : une nouvelle réplique doit démarrer le système d'exploitation, lancer le processus, charger la configuration, préchauffer les caches et passer les contrôles d'état. Cela peut prendre de 5 secondes (redémarrage de conteneur) à 5 minutes (démarrage complet de la VM + récupération de l'image). Le dimensionnement automatique ne peut pas répondre aux pics de charge plus courts que ce délai.

Vidage lent : une réplique retirée du pool a besoin de temps pour terminer les requêtes en cours avant d'être terminée. Sinon, les utilisateurs voient des réponses tronquées. Les proxies inverses prennent en charge le vidage (cesser d'accepter de nouvelles requêtes, terminer les requêtes actives), mais cela prend de quelques secondes à quelques minutes.

Pool chaud : les flottes de production maintiennent un pool de répliques pré-provisionnées mais inactives, prêtes à prendre le trafic sur signal. Cela échange un coût fixe modeste contre une réponse rapide aux pics de charge.

Évacuation des connexions (draining) vs arrêt immédiat : l'arrêt gracieux est essentiel. Un SIGTERM qui déclenche l'évacuation prend plus de temps qu'un SIGKILL, mais ne casse pas les requêtes des utilisateurs.

Fenêtre de vérification de santé : une réplique qui vient de démarrer peut passer sa première vérification de santé avant que son pool de connexions à la base de données ne soit « chaud » ; le proxy envoie alors du trafic réel et les douze premières requêtes sont lentes. Réglez les vérifications de santé pour tester le chemin réel, et non seulement la vitalité du processus.

Glissement vers la persistance d'état (stickiness creep) : même les couches nominativement sans état acquièrent de la persistance avec le temps (caches CDN, caches de résolveurs DNS, pools de connexions). Soyez sceptique envers des « répliques identiques » qui se comportent pourtant différemment.

Pool chaud ou mise à l'échelle réactive ?

Votre API de métadonnées vidéo (la même que dans la question précédente, dimensionnée à 51 répliques pour le pic stable + les pics de charge) subit un pic de charge de 30 secondes à 5 fois la charge normale à chaque fois qu'une nouvelle vidéo virale est téléversée. La mise à l'échelle automatique prend actuellement 90 secondes pour ajouter une nouvelle réplique à froid (téléchargement de l'image + échauffement). Pendant cet intervalle de 90 secondes, la latence augmente fortement et certaines requêtes expireront.

Proposez une solution. Choisissez : (a) conserver la mise à l'échelle réactive mais l'ajuster différemment, (b) provisionner un pool chaud de répliques inactives, ou (c) provisionner statiquement pour le pic de 5x en permanence. Justifiez votre choix et nommez un coût que les deux autres options imposeraient.

Concevoir une couche sans état sous contraintes

Synthèse

Vous avez appris pourquoi le dimensionnement horizontal l'emporte au-delà d'un certain seuil, ce que signifie l'état en pratique, comment dimensionner une flotte en fonction de la charge attendue et des pics de charge, et où le dimensionnement horizontal atteint ses limites.

Appliquez les quatre.

Concevez une couche backend pour feed.example.com, une API de flux social. Contraintes : capacité par réplique de 200 req/s à 70 % de CPU ; charge de pointe attendue de 1500 req/s ; facteur de pic de 2,5x (histoires tendance occasionnelles) ; survivre à 2 pannes simultanées de répliques ; temps de démarrage à froid de 60 secondes ; les pics peuvent durer 45 secondes ; le budget permet une certaine capacité inactive, mais pas une provision permanente de 2,5x.

Dimensionnez la flotte en régime permanent (montrez les calculs), choisissez votre stratégie de gestion des pics (pool chaud / auto-échelle réactive / les deux) & identifiez un élément d'état par utilisateur que l'application détient probablement & où il doit être déplacé pour garder la couche sans état.

Où va ce cours ensuite

Vers où va ce cours

Vous disposez désormais d'un modèle mental fonctionnel d'une couche sans état : pourquoi elle est évolutive, comment la dimensionner, ce qui casse à ses limites, et où l'état doit se déplacer lorsque vous poussez cette couche au-delà de la couche qui doit croître.

La prochaine leçon de ce cours (cs_distsys_ingress_egress_separation) aborde un problème plus subtil : même une couche sans état parfaitement dimensionnée peut échouer de manière inattendue lorsque le trafic entrant et sortant partagent le même chemin réseau. L'exemple classique implique un proxy qui tente de se connecter à lui-même ; la solution consiste à diviser une couche en deux avec des responsabilités distinctes.

Leçon complémentaire : geometry_of_stateless_horizontal_scaling dérive la courbe de mise en file d'attente, applique la loi de Little à une flotte de répliques, et explique la signification géométrique du point d'inflexion à 80 % de charge.

Bravo. En avant.