Le hameau ne décide pas de devenir une métropole

Trois fermes, un chemin de terre, un puits. Personne ne demande au puits combien de foyers il peut servir : il y en a six, et il les sert.

Une gare ouvre à deux kilomètres. Un lotissement sort de terre, puis un second. Le puits est à sec tous les soirs d'août, alors la commune creuse un forage et pose une conduite. Le forage attire d'autres habitants, la conduite passe sous une rue qui devient une avenue, et l'avenue finit par exiger un feu tricolore.

Personne n'a décidé de bâtir une ville. On a réagi, chaque fois, parce que quelque chose faisait mal, et chaque réponse a fabriqué la gêne suivante.

Un système logiciel descend exactement la même pente. Le premier jour, l'application et sa base tiennent sur une machine à quelques euros par mois. Un million d'utilisateurs plus tard, il y a un répartiteur de charge, des réplicas, un cache, un réseau de distribution de contenu, une file de messages et une base découpée. Entre les deux, neuf étapes.

Aucune de ces neuf étapes ne se justifie par elle-même. Chacune répond à une douleur qu'on a mesurée, et chacune fabrique la douleur qui déclenchera la suivante.

Ce billet est la carte. Les billets des paliers 2 et 3 en détaillent chaque case.


Les neuf étapes, nommées d'emblée

Voici la trajectoire complète, sans suspense : serveur unique, séparation de l'application et de la base, répartiteur de charge devant plusieurs instances, réplication de la base, cache, réseau de distribution de contenu, plusieurs centres de données, file de messages, partitionnement de la base.

flowchart LR E1["1. Serveur unique"] --> E2["2. Application et base séparées"] E2 --> E3["3. Répartiteur et instances multiples"] E3 --> E4["4. Réplication de la base"] E4 --> E5["5. Cache"] E5 --> E6["6. CDN"] E6 --> E7["7. Plusieurs centres de données"] E7 --> E8["8. File de messages"] E8 --> E9["9. Base partitionnée"] classDef hameau fill:#b5651d,stroke:#8a4d16,color:#f5f2ec; classDef ville fill:#d89253,stroke:#8a4d16,color:#2c3338; classDef metropole fill:#ede9e1,stroke:#8a4d16,color:#2c3338; class E1,E2,E3 hameau; class E4,E5,E6 ville; class E7,E8,E9 metropole;

Avant d'aller plus loin, posons le chiffre qui remet la trajectoire à sa place. Un million d'utilisateurs inscrits, sur un service où l'on suppose 20 % d'actifs par jour et 30 requêtes par utilisateur actif :

1 000 000  utilisateurs inscrits
x 20 %     actifs par jour              =   200 000  utilisateurs actifs
x 30       requêtes par utilisateur     = 6 000 000  requêtes par jour
/ 100 000  (86 400 s, arrondi)          =        60  requêtes par seconde
x 3        facteur de pic assumé        =       180  requêtes par seconde au pic

La division par 100 000 au lieu de 86 400 sous-estime d'environ 13 %. Sans importance ici : on cherche 60, pas 69,4. Les 20 % et le facteur de pic à 3 sont des hypothèses assumées, écrites noir sur blanc pour qu'on puisse les contester. La méthode complète est dans L'estimation à la louche : chiffrer avant de construire.

180 requêtes par seconde, ce n'est pas beaucoup. Une machine bien réglée les encaisse. Ce qui force la montée en charge, c'est autre chose : le volume de données accumulé, la latence pour l'utilisateur situé à 9 000 kilomètres, et le refus de couper le service pendant qu'une machine redémarre.

Un million d'utilisateurs ne fabrique pas 100 000 requêtes par seconde. On ne monte presque jamais en charge à cause du débit moyen, mais à cause du pic, du volume stocké et de l'indisponibilité qu'on n'accepte plus.

Sortir du hameau : les étapes 1 à 3

Dans un hameau, la mairie, l'école et la salle des fêtes tiennent dans le même bâtiment. Cela marche très bien, jusqu'au jour où le conseil municipal se réunit pendant la kermesse.

Étape 1 : le serveur unique

Une machine. Le serveur web, le code applicatif, la base de données et les fichiers téléversés par les utilisateurs. Le DNS renvoie l'adresse publique de la machine, le navigateur tape dessus, c'est tout.

Ce que ça permet est sous-estimé. Un seul déploiement, un seul journal à lire, une seule connexion SSH pour diagnostiquer n'importe quoi, aucune latence réseau interne, un coût dérisoire. Pour un service qui n'a pas encore prouvé qu'il servait à quelqu'un, c'est la bonne architecture.

Ça casse sur trois points. Une requête SQL mal écrite mange le processeur et fait ramer le serveur web, parce qu'ils partagent la même machine. Redémarrer l'application implique de toucher au processus voisin de la base. Et la panne de la machine, c'est la panne du service, avec une restauration de sauvegarde à la main pendant que le téléphone sonne.

Étape 2 : séparer l'application de la base

Deux machines : la logique d'un côté, la donnée de l'autre. C'est la séparation la moins spectaculaire de la liste et l'une des plus rentables.

Elle débloque le dimensionnement indépendant. Une base veut de la mémoire et des entrées-sorties rapides ; un serveur applicatif veut du processeur. Sur une seule machine, on achète les deux profils en même temps et on paie pour rien.

Elle débloque aussi le cycle de vie : on redéploie l'application dix fois par jour sans jamais frôler le processus qui détient les données.

Le prix, c'est un aller-retour réseau sur chaque requête. Il faut un pool de connexions côté application, et surveiller la latence entre les deux machines comme on surveillerait un disque.

Étape 3 : le répartiteur de charge et les instances multiples

Le carrefour a maintenant un agent de circulation. Les voitures ne choisissent plus leur voie toutes seules, on leur indique celle qui est libre.

Concrètement : plusieurs instances identiques de l'application, un répartiteur de charge devant, et le DNS qui pointe désormais sur le répartiteur. Celui-ci interroge la santé de chaque instance et retire de la rotation celle qui ne répond plus.

Le gain de sécurité arrive en prime. Les serveurs applicatifs quittent l'internet public et ne portent plus que des adresses privées ; seul le répartiteur est joignable de l'extérieur. Le détail des trois intermédiaires qu'on confond en permanence est dans Répartiteur de charge, proxy et proxy inverse.

Une condition, et elle n'est pas optionnelle : les instances doivent être sans état. Une session stockée dans la mémoire d'un serveur oblige à renvoyer l'utilisateur toujours au même, ce qui annule la moitié du bénéfice. Les sessions partent dans un magasin partagé, comme l'explique Couplage lâche, stateless et cache.

Les trois premières étapes n'achètent pas de la performance, elles achètent de la continuité. Après l'étape 3, on déploie en pleine journée et on perd une machine sans que personne ne s'en aperçoive.

Absorber la lecture : les étapes 4 à 6

Une ville consulte infiniment plus qu'elle ne modifie. Des milliers de gens lisent les horaires affichés à l'arrêt de bus, une poignée d'agents les changent.

Étape 4 : la réplication de la base

L'acte original est signé à l'hôtel de ville. Les copies conformes, elles, se retirent dans n'importe quelle mairie annexe.

Une base primaire reçoit les écritures, un ou plusieurs réplicas reçoivent une copie du flux d'écriture et servent les lectures. L'application doit alors savoir router chaque requête.

Opération Destination Raison
Écriture La base primaire, toujours Une seule source de vérité pour l'ordre des écritures
Lecture courante Un réplica C'est là qu'est le volume, et il se répartit sur les réplicas
Lecture juste après l'écriture du même utilisateur La base primaire, forcée Sinon un réplica en retard renvoie l'état d'avant le clic

La troisième ligne est celle qu'on découvre en production. La réplication n'est pas instantanée, et l'utilisateur qui vient de modifier son profil ne comprend pas pourquoi la page suivante affiche l'ancien nom.

Bénéfice secondaire, considérable : un réplica à jour est un candidat à la promotion le jour où la primaire tombe. La mécanique de bascule, avec ses pièges, est traitée dans Haute disponibilité : redondance, réplication, bascule.

Étape 5 : le cache

L'épicerie du coin ne remplace pas l'entrepôt central. Elle garde à portée les vingt références que tout le quartier achète tous les jours.

Un cache se place entre l'application et la base. L'application demande d'abord au cache ; s'il détient la valeur, c'est un succès et la base n'est jamais jointe. Sinon, c'est un défaut : on lit la base, on écrit le résultat dans le cache avec une durée de vie, et on répond.

Reprenons le pic calculé plus haut, en supposant 90 % de lectures et un taux de succès de 90 % :

180 requêtes/s au pic
x 90 %  de lectures                = 162  lectures/s
x 10 %  de défauts de cache        =  16  lectures/s atteignent la base

La base passe de 162 à 16 lectures par seconde. C'est un facteur dix pour le prix d'une brique en mémoire.

Ce que ça coûte : une donnée qui peut être périmée, une deuxième source de vérité à raisonner, et un scénario nouveau. Le jour où le cache redémarre à vide, les 162 lectures par seconde arrivent d'un coup sur une base qui n'y est plus habituée. Ce cas, et les patterns d'accès qui vont avec, sont le sujet de Cache distribué et CDN : rapprocher la donnée du lecteur.

Étape 6 : le réseau de distribution de contenu

L'entrepôt de quartier, cette fois. On y dépose ce que toute la ville consomme, pour ne pas faire monter chaque livraison depuis le port.

Un CDN sert les fichiers statiques (images, scripts, feuilles de style, vidéos) depuis un point de présence proche de l'utilisateur. Le premier visiteur d'une région provoque un défaut, le fichier est récupéré depuis l'origine et conservé sur place ; les suivants sont servis localement.

Critère Cache distribué Réseau de distribution de contenu
Ce qu'il garde Résultats de requêtes, objets métier, sessions Fichiers publics : images, scripts, feuilles de style
Où il se trouve Dans le centre de données, à côté de l'application Aux points de présence, près de l'utilisateur
Ce qui le vide Durée de vie, éviction, invalidation applicative Durée de vie et purge explicite, propagée à tous les points

Après cette étape, les serveurs applicatifs ne voient plus passer que du dynamique. La bande passante sortante, qui était le premier poste de coût, est absorbée par une couche dont c'est le métier.

Les étapes 4 à 6 traitent toutes le même déséquilibre : on lit beaucoup, on écrit peu. Elles ne règlent rien du côté des écritures, qui restent concentrées sur une seule machine.

Passer à l'échelle de la métropole : les étapes 7 à 9

La ville a débordé de sa vallée. On fonde un quartier sur l'autre rive, avec sa propre distribution d'eau, et on découvre que gérer deux réseaux n'est pas gérer un réseau deux fois plus grand.

Étape 7 : plusieurs centres de données

Un site unique, c'est deux problèmes : la latence pour ceux qui sont loin, et l'existence d'un lieu physique dont la perte emporte tout.

Le principe est simple : chaque utilisateur est routé vers le site le plus proche, généralement par résolution DNS géographique. En cas d'incident sur un site, on redirige son trafic vers l'autre.

Le travail réel est ailleurs. Il faut synchroniser les données entre sites, décider ce qu'on réplique et ce qu'on ne réplique pas (la résidence des données peut l'interdire), et tester le basculement autrement qu'en théorie. Chaque site doit aussi savoir tourner à charge doublée, sinon la bascule finit le travail que la panne avait commencé.

C'est ici qu'on rencontre la question de fond de la série : quand le lien entre les deux sites se coupe, refuse-t-on le service ou répond-on avec une donnée peut-être périmée ? C'est le sujet du billet suivant, Le théorème CAP, ACID et BASE : ce qu'on accepte de perdre.

Étape 8 : la file de messages

Le centre de tri postal ne distribue rien. Il encaisse tout ce qui arrive, range dans des casiers, et laisse les tournées partir à leur rythme.

Une file de messages sort du chemin de la requête tout ce qui n'a pas besoin d'y rester : redimensionnement d'image, envoi de courriel, indexation pour la recherche, notification, calcul de statistiques. L'application publie un message et répond immédiatement ; des travailleurs consomment la file de leur côté.

Deux bénéfices distincts. Le pic est absorbé, parce qu'une file qui s'allonge est un symptôme, pas une panne. Et les travailleurs se dimensionnent indépendamment du web : dix instances de traitement d'image quand la file monte, deux la nuit.

Le prix, c'est le différé. L'utilisateur ne voit plus le résultat dans la seconde, il faut donc l'afficher comme tel dans l'interface. Il faut aussi traiter les échecs et les rejeux, ce qui impose des consommateurs idempotents. Le fonctionnement interne du log distribué est détaillé dans Files de messages et Kafka : découpler pour tenir.

Étape 9 : partitionner la base

La ville se découpe en arrondissements le jour où une seule mairie ne peut plus recevoir tout le monde.

Avant d'en arriver là, on aura fait grossir la machine : plus de mémoire, un disque plus rapide, plus de cœurs. C'est la mise à l'échelle verticale, et elle mène loin. Elle s'arrête au moment où il n'existe plus de machine plus grosse à acheter, ou bien où son prix devient absurde.

Reste le découpage. Verticalement, on sort des tables ou des colonnes entières vers une autre base, souvent en suivant les frontières fonctionnelles. Horizontalement, on répartit les lignes d'une même table sur plusieurs machines selon une clé de partition : c'est le sharding.

Ce qu'on perd est important : les jointures entre partitions, les transactions qui traversent deux partitions, et l'unicité globale des identifiants qu'il faut désormais garantir autrement. Et la clé de partition devient la décision la plus lourde de conséquences du système entier. Tout cela est traité dans Sharding et hachage cohérent : découper sans tout déménager.

Les étapes 7 à 9 ne se rattrapent pas dans l'urgence. Un partitionnement décidé pendant l'incident produit une clé qu'on gardera dix ans.

La carte complète

Voici à quoi ressemble le système une fois les neuf étapes franchies.

flowchart TB U["Navigateur"] --> CDN["CDN
fichiers statiques"] U --> LB["Répartiteur de charge"] LB --> APP["Ferme applicative
sans état"] APP --> CA["Cache distribué"] CA --> REP["Réplicas
lectures"] APP --> PRI["Base primaire
écritures"] APP --> MQ["File de messages"] MQ --> WK["Travailleurs asynchrones"] WK --> PRI PRI --> REP classDef bord fill:#b5651d,stroke:#8a4d16,color:#f5f2ec; classDef app fill:#d89253,stroke:#8a4d16,color:#2c3338; classDef data fill:#ede9e1,stroke:#8a4d16,color:#2c3338; class U,CDN,LB bord; class APP,MQ,WK app; class CA,PRI,REP data;

Et voici la même trajectoire sous forme de tableau, avec ce que chaque étape apporte et ce qu'elle prélève.

Étape La douleur qui la déclenche Ce qu'elle apporte Ce qu'elle coûte
1. Serveur unique Aucune, c'est le point de départ Un déploiement, un journal, un coût dérisoire Toute panne est totale
2. Base séparée Base et application se disputent la machine Dimensionner et redémarrer chaque côté séparément Un aller-retour réseau par requête
3. Répartiteur et instances multiples Le service tombe avec sa seule machine, le processeur sature au pic Continuité pendant les déploiements et les pannes L'application doit devenir sans état
4. Réplication Les lectures saturent la base Lectures réparties, réplica promouvable Retard de réplication, lectures périmées
5. Cache Les mêmes lectures reviennent en boucle La base ne voit plus qu'une fraction du trafic Donnée périmée, invalidation, cache froid
6. CDN Le statique mange la bande passante, la latence pénalise les lointains Fichiers servis près de l'utilisateur Purge à gérer, coût au volume sortant
7. Plusieurs centres de données Un site unique, donc une latence subie et un risque concentré Proximité géographique et survie à la perte d'un site Synchronisation des données, bascule à tester
8. File de messages Des traitements lents bloquent la requête Pics absorbés, travailleurs dimensionnés à part Traitement différé, rejeux à rendre idempotents
9. Partitionnement Une machine ne porte plus la base Écritures et volume répartis Jointures et transactions inter-partitions perdues
Chaque ligne de la colonne de droite est une dette d'exploitation. Neuf étapes franchies, c'est neuf systèmes à superviser, à sauvegarder et à réveiller à trois heures du matin.

Le piège inverse : franchir une étape trop tôt

Un hameau de deux cents habitants qui construit un échangeur autoroutier a gaspillé son budget et coupé son village en deux, pour un trafic qui n'arrivera jamais.

Les versions logicielles de cet échangeur se ressemblent toutes. Une base partitionnée en quatre pour une table de dix mille lignes. Un bus de messages déployé, supervisé et sauvegardé pour trois notifications par jour. Deux centres de données actifs avant même qu'un contrôle de santé correct existe sur le premier. Un cache posé devant une requête qui répondait en deux millisecondes.

Le coût dépasse largement l'installation : la latence supplémentaire de chaque saut inutile, les modes de panne nouveaux, la sauvegarde d'une brique de plus, et surtout le fait que personne n'ose retirer ce qui a été mis. Une complexité ajoutée se retire beaucoup plus difficilement qu'elle ne s'ajoute.

La règle tient en une phrase : on franchit une étape quand une mesure le demande, pas quand un schéma d'architecture le suggère. Une saturation observée, une latence au 99e centile qui dérive, un coût qui explose, une panne qu'on ne veut plus revivre.

Le corollaire est aussi utile : une étape franchie doit rester réversible aussi longtemps que possible. Un cache qu'on peut débrancher, des lectures qu'on peut renvoyer sur la primaire, une file qu'on peut court-circuiter. C'est ce qui permet de tester une étape sans la subir.

La question n'est jamais « quelle est la bonne architecture pour un million d'utilisateurs ». Elle est « quel est le prochain point qui va céder, et à partir de quel chiffre ».

Scénario B : la pile d'Instagram en 2011

En 2011, l'équipe technique d'Instagram publie l'inventaire complet de sa pile : What Powers Instagram: Hundreds of Instances, Dozens of Technologies. Le service annonce alors plus de 14 millions d'utilisateurs après un peu plus d'un an d'existence, servis par trois ingénieurs.

Le billet énonce d'abord ses principes : garder les choses très simples, ne pas réinventer la roue, préférer des technologies éprouvées et solides quand c'est possible. Ces trois lignes expliquent tout le reste du texte.

Le texte décrit un système arrivé aux étapes 3 à 6, 8 et 9 de la trajectoire, raconté par ceux qui les ont franchies.

Étape 3. Le trafic entre par le répartiteur de charge managé d'Amazon, qui distribue vers trois instances nginx retirées et remises en rotation selon leur contrôle de santé. La terminaison TLS se fait au niveau du répartiteur, ce qui allège le processeur de nginx. Derrière, des serveurs applicatifs qui font tourner Django sous Gunicorn, sur des machines EC2 avec Ubuntu, déployés en parallèle avec Fabric.

Étapes 4 et 9. PostgreSQL pour les utilisateurs, les métadonnées de photos et les tags, déjà réparti sur plusieurs partitions, avec des réplicas maintenus par la réplication en continu de Postgres. PgBouncer se place devant pour mutualiser les connexions.

Étape 5. Deux caches pour deux usages. Memcached pour la mise en cache générale, Redis pour le fil principal, le fil d'activité et les sessions, avec des jeux de données pensés pour tenir en mémoire.

Étape 6. Les photos ne sont jamais servies par les serveurs applicatifs. Elles vivent dans le stockage objet S3 et sont distribuées par CloudFront.

Étape 8. Gearman comme file de tâches, avec environ deux cents travailleurs Python, pour tout ce qui n'a pas à bloquer la requête : partage vers Twitter et Facebook, notification des abonnés temps réel, diffusion du fil aux abonnés. Le billet cite aussi Solr pour la recherche géographique, et Munin, Pingdom, PagerDuty et Sentry pour la supervision et l'alerte.

Deux enseignements. Aucune brique n'est exotique : ce sont, une par une, les réponses classiques aux douleurs classiques. Et rien n'a été construit d'avance. Une petite équipe qui sert des millions d'utilisateurs ne l'a pas fait en devinant l'architecture finale, elle l'a fait en franchissant les étapes dans l'ordre où elles se présentaient.

La pile décrite en 2011 tient sur une page, et c'est ce qui la rend crédible. Une architecture qui a suivi les douleurs reste lisible ; une architecture qui a suivi les modes ne l'est jamais.

Points clés

  • La trajectoire de zéro à un million passe par neuf étapes. Chacune est déclenchée par une douleur mesurée, et chacune fabrique la suivante.
  • Un million d'utilisateurs, avec les hypothèses posées ici, fait 60 requêtes par seconde en moyenne et 180 au pic. Ce n'est presque jamais le débit moyen qui force la montée en charge.
  • Les étapes 1 à 3 achètent de la continuité, les étapes 4 à 6 absorbent la lecture, les étapes 7 à 9 s'attaquent au volume, à l'écriture et à la géographie.
  • Chaque étape franchie ajoute une brique de plus à exploiter, donc à superviser et à sauvegarder. La colonne « ce qu'elle coûte » du récapitulatif est une dette d'exploitation.
  • Le piège le plus fréquent n'est pas de franchir une étape trop tard, c'est de la franchir des années trop tôt et de ne plus pouvoir revenir en arrière.

Dans la série

Palier 1 : Dimensionner la ville. Domaine : Mise à l'échelle.


Pour aller plus loin