Deux Façons de Transporter Plus de Charge
Bienvenue
Lorsqu'un service commence à plier sous la charge, un opérateur fait face à un choix. Augmenter l'existence de la machine existante (plus de CPU, plus de RAM, disques plus rapides). Ou ajouter plus de machines qui font chacune le même travail.
La première voie passe par l'échelle verticale (hausser). La seconde passe par l'échelle horizontale (élargir).
Cette leçon enseigne pourquoi presque toute architecture web moderne choisit l'horizontale et quelle propriété du charge travaille cette choix viable. La réponse se cache dans un mot : l'état.
Au final, vous comprendrez:
- Les courbes de coût vertical vs horizontale & où chacun fait sens
- Ce que 'état' et 'sans état' signifient en pratique, & pourquoi l'un d'entre eux se multiplie bon marchément
- Le calcul qui dimensionne une flotte de replicas sous charge prévue & charge de surcroît
- La règle de tête qui empêche un niveau de s'effondrer au-delà du genou de la file d'attente
- Où l'état doit vivre (il n'est jamais invisible) et comment le pousser hors des couches qui doivent s'agrandir
Pourquoi l'Horizontale Gagne Au-Delà d'un Seuil
Échelle Verticale : Une Boîte Plus Grande
Avantages : simple. Aucun changement de code. Aucune coordination. Le même processus a maintenant plus de CPU.
Inconvénients : plafond. La VM commercialement disponible la plus grande a fini RAM & noyaux. Au-delà, aucun argent achète plus d'espace pour la tête. Les coûts vont superlinéairement au-delà du point doux d'une offre d'un fournisseur. Une échec de cette seule machine prend tout le service en bas.
Échelle Horizontale : Beaucoup de Boîtes Plus Petites
Avantages : pas de plafond (jusqu'à votre volonté de payer pour & coordonner des machines). La capacité ajoute linéairement avec le nombre de replicas, prévisiblement. Une seule machine échoue 1/N de capacité, pas 100%.
Inconvénients : nécessite le travail à soutenir. Certaines charges (une grande base de données unique, un serveur d'jeu d'état tenant des sessions en vie) résistent à l'échelle horizontale. La coordination & la distribution de la charge deviennent des soucis opérationnels.
Le crossover : toute service de production qui doit résister à l'échec d'une machine unique doit fonctionner sur au moins deux machines. Une fois que vous acceptez deux, vous avez déjà choisi l'échelle horizontale. D'ici, la question n'est pas 'devrions-nous?' mais 'comment ajouterons-nous le prochain clone aussi peu cher que possible?'
L'élément clé : un charge de travail qui n'entretient pas d'état par demande sur la machine elle-même. Alors, n'importe quel clone peut répondre à n'importe quelle demande, & ajouter un clone ajoute de la capacité sans coordination.
Étatiel vs Étatless en pratique
L'état ne disparaît jamais, il se contente de bouger
Composant étatiel : conserve des informations dont la perte modifierait le comportement. Une base de données conservant les comptes d'utilisateurs. Une cache conservant des jetons de session. Un travailleur fixant une connexion en streaming longue à une utilisation spécifique.
Composant étatless : ne conserve aucune information dont la perte serait importante. Un niveau web qui lit une demande, consulte une base de données et écrit une réponse. Chaque demande se tient debout ; le niveau ne se souvient de rien entre les demandes.
Intuition 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 magasin d'objets). Les couches qui font face au trafic peuvent alors devenir étatless, et les couches étatless s'échelonnent horizontalement car n'importe quel clone peut répondre à n'importe quelle demande.
Test pratique : si vous tuiez aléatoirement un processus dans ce niveau et le redémarriez, serait-il quelqu'un à connaître une mauvaise réponse ou une session perdue ? Si oui, il conserve de l'état. Si non, il ne conserve pas d'état.
Exemples
- Un processus web Python qui lit des demandes, consulte Postgres, retourne JSON : étatless. L'état vit dans Postgres.
- Un processus web Python qui conserve les paniers d'achat des utilisateurs dans une mémoire locale : étatiel. La perte du processus entraîne la perte des paniers.
- Un serveur WebSocket qui conserve des connexions ouvertes aux utilisateurs de chat : étatiel au sens de la connexion. La perte du processus entraîne la perte des connexions ; les clients doivent se reconnecter. Ces derniers peuvent souvent s'échelonner horizontalement avec soin (sessions collantes, hachage cohérent)
- Une cache Redis avant un Postgres : étatique pour les contenus de la cache, mais acceptable si les manques de cache sont supportables. Une perte de replica signifie une erreur de cache, pas une perte de données.
Conception pour l'échelle horizontale = déplacer l'état hors du niveau qui doit s'échelonner.
Auditer un niveau suspect
Une équipe gère une API de recommandation sur 6 VM de backend derrière un proxy inversé. L'application : lit un ID d'utilisateur dans la requête, récupère l'activité récente de l'utilisateur à partir de Postgres, exécute un algorithme de scoring, retourne une liste d'articles recommandés. Deux comportements non standards :
- L'application conserve une 'cache d'activité récente de l'utilisateur' en mémoire, initialisée lors de la première requête pour un utilisateur, réutilisée lors des requêtes ultérieures.
- L'application utilise les sessions collantes : une fois qu'un utilisateur rencontre la VM #3, toutes les requêtes ultérieures de l'utilisateur sont dirigées vers la VM #3 (le proxy est configuré pour la routage collant sur une cookie).
La formule des replicas
La formule de capacité la plus simple
Une fois que le niveau devient sans état, sa taille devient une simple arithmétique. Vous avez besoin d'autant de replicas pour que la charge en état constant arrive et s'éloigne à la même vitesse, avec un marge pour les surcharges.
La formule:
replicas = ⌈ (peak_load × surge_factor) / per_replica_capacity ⌉ + headroom
Où :
- peak_load: demande maximale en requêtes/secondes que vous attendez en opération normale
- surge_factor: un multiplicateur couvrant de courtes hausses au-dessus de la charge maximale (généralement 1,5x à 2x pour un trafic prévisible, 3x ou plus pour viral / imprévisible)
- per_replica_capacity: requêtes/secondes qu'une replica gère avec un délai d'acceptabilité et une utilisation (généralement mesuré à 70% de CPU, pas à saturation)
- headroom: replicas supplémentaires pour que quelques perte de replica ne fasse pas s'effondrer le niveau (généralement 1-2 replicas pour de petites flottes, 10-20% pour les plus grandes)
Exemple de travail: un backend gère 100 req/s avec 70% de CPU par réplica. La charge maximale est de 600 req/s. Vous prévoyez des surges occasionnels de 2 fois la charge. Vous souhaitez survivre à 2 perte de réplica.
réplicas = ⌈ (600 × 2) / 100 ⌉ + 2 = 12 + 2 = 14 réplicas
La règle des 80%
La capacité par réplica n'est pas le point de saturation. Mesurez la capacité à 70-80% de CPU, jamais à 100%.
Au-delà de 80% d'utilisation, les courbes de files d'attente montent fortement : une file qui prenait 10 ms à 60% d'utilisation prend 80 ms à 90% d'utilisation. La latence, et non le débit, cède en premier. (La leçon complémentaire geometry_of_stateless_horizontal_scaling démonte cette courbe mathématiquement.)
L'auto-échelle vs La provisionnement statique
Statique : prévoir pour le pic × l'espace réservé pour les surges et accepter le coût de fonctionnement à faible utilisation en dehors des heures creuses.
Auto-échelle : un contrôleur ajoute et supprime des réplicas en fonction de l'utilisation observée, de la cible latence ou de la profondeur de la file.
Avertissement sur l'auto-échelle : le temps d'accueil d'un nouveau réplica compte. Si un nouveau réplica prend 2 minutes pour démarrer, l'auto-échelle ne peut pas répondre à une surges de 30 secondes. Une auto-échelle mature maintient un pool de réplicas pré-provisionnés juste en dessous du seuil de mise à niveau.
Dimensionner un parc pour un nouveau service
Your team plans to launch a video metadata API. Benchmarks show a single replica handles 250 req/s at 70% CPU & 50 ms p99 latency. Marketing forecasts peak load at 4,000 req/s during prime-time hours. A planned promotional event could surge to 3x peak briefly. You want the service to survive 3 simultaneous replica failures without exceeding 80% utilization on the survivors.
Le temps de démarrage froid, la vidange lente et autres bords réels
Les véritables parcs ont des bords réels
La formule suppose que les réplicas apparaissent instantanément, acceptent le trafic instantanément et déchargent le trafic instantanément. Aucune de ces conditions ne sont réelles en production.
Démarrage froid : une nouvelle réplique doit démarrer le système d'exploitation, lancer le processus, charger la configuration, réchauffer les caches et passer les vérifications de santé. Entre 5 secondes (redémarrage du conteneur) et 5 minutes (démarrage complet du VM + téléchargement de l'image). L'auto-échelle ne peut pas répondre à des surcharges plus courtes que ce délai.
Tirasse longue : une réplique en cours de suppression a besoin de temps pour terminer les demandes en cours avant de se terminer. Sinon, les utilisateurs voient des réponses tronquées. Les reverse proxies prennent en charge la vidange (arrêt de l'acceptation de nouvelles demandes, fin des demandes actives) mais cela prend des secondes à des minutes.
Piscine chaude : les flottes de production maintiennent une piscine de répliques pré-provisionnées mais inactives prêtes à prendre le trafic sur signal. Il échange une petite coût constant pour une réponse rapide à la surcharge.
Évacuation douce vs kill immédiat : la fermeture gracieuse compte. Une SIGTERM qui déclenche la vidange prend plus de temps qu'une SIGKILL mais ne brise pas les demandes de l'utilisateur.
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 sa connexion de base de données ne soit chaude ; le proxy envoie alors un trafic réel et les premières douzaines de demandes sont lentes. Ajustez les vérifications de santé pour tester le véritable chemin, pas seulement la vivacité du processus.
Effet de glaise : même les niveaux nominativement étatiques acquièrent de la glaise au fil du temps (cache de CDN, résolveurs de DNS, pools de connexions). Soyez méfiants des 'mêmes répliques' qui comportent cependant différemment.
Piscine chaude ou auto-échelle réactive ?
Votre API de métadonnées vidéo (la même que dans la question précédente, dimensionnée pour un pic constant de 51 répliques + surges) subit une surge de 30 secondes à 5 fois la charge normale chaque fois qu'une nouvelle vidéo virale est publiée. L'auto-échelle prend actuellement 90 secondes pour ajouter une nouvelle réplique à partir de froid (téléchargement d'image + réchauffement). Durant les 90 secondes d'intervalle, la latence augmente fortement et certaines demandes expirent.
Concevez un niveau sans état sous contraintes
Synthèse
Vous avez appris pourquoi l'échelle horizontale gagne au-delà d'un certain seuil, ce que l'état signifie en pratique, comment dimensionner un ensemble sous charge attendue & de surcharge, & où l'échelle horizontale manque à gagner.
Appliquez les quatre.
Concevez un niveau arrière pour feed.example.com, une API de flux social. Contraintes : capacité par réplica de 200 req/s à 70% de CPU; charge maximale prévue de 1500 req/s; facteur de surcharge de 2,5x (histoires tendance occasionnelles) ; résistance à 2 perte de réplica simultanées ; temps de démarrage froid de 60 secondes ; les surbrusques peuvent durer 45 secondes ; le budget permet une certaine capacité inutilisée, mais pas une provisionnement de 2,5x permanent.
Où Ce Cours Va Ensuite
Où Ce Cours Va Ensuite
Vous avez maintenant un modèle mental de travail d'un niveau sans état : pourquoi il s'agrandit, comment le dimensionner, ce qui manque à gagner à ses bords & où l'état doit bouger quand vous le poussez hors du niveau qui doit grandir.
La prochaine leçon dans ce cours (cs_distsys_ingress_egress_separation) traite d'un problème plus subtil : même un niveau sans état parfaitement dimensionné peut échouer de manière surprenante lorsqu'un trafic entrant & sortant emprunte la même voie réseau. L'exemple classique implique un proxy qui tente de se connecter à lui-même ; la solution implique la division d'un niveau en deux avec différentes responsabilités.
Leçon complémentaire : geometry_of_stateless_horizontal_scaling dérive la courbe de file d'attente, la loi de Little appliquée à un ensemble de répliques & la signification géométrique du genou à 80% d'utilisation.
Bien fait. En avant.