Répartition de charge
Du DNS au routage SQL
Cinq niveaux, par ordre croissant de finesse et de coût d'exploitation. Et un paradoxe que les plaquettes ne mentionnent jamais : le répartiteur devient lui-même le point de panne.
Le paradoxe du répartiteur
On installe un répartiteur pour que la charge se distribue et que la panne d'un serveur ne soit plus un incident. Et ce faisant, on place devant tout le reste un composant unique par lequel chaque requête doit passer. Le service tolère désormais la perte de n'importe lequel de ses serveurs applicatifs, et ne tolère plus la perte du répartiteur.
La réponse habituelle est de le redonder à son tour — deux répartiteurs, une adresse flottante entre eux — ce qui revient à empiler une architecture de haute disponibilité sous celle de répartition. C'est faisable, c'est ce que nous déployons le plus souvent, mais il faut savoir qu'on a alors deux mécanismes à exploiter plutôt qu'un.
C'est pour ça que l'échelle commence en bas
Les deux premiers niveaux — round-robin DNS et anycast — n'ajoutent aucun composant central dans le chemin des requêtes. Le DNS et le routage ont bien sûr leur propre infrastructure, mais elle est déjà là et déjà redondée : ces deux niveaux répartissent sans rien créer de neuf à exploiter. Ils sont grossiers, mais ils sont gratuits en surface de panne. Les trois niveaux suivants sont plus fins et introduisent chacun une pièce dont il faudra s'occuper. Le bon réflexe n'est donc pas de commencer par HAProxy[1], c'est de commencer aussi bas que le besoin le permet.
Ce sujet est le prolongement direct de nos architectures de haute disponibilité : la répartition répond à « comment servir plus de trafic qu'une machine n'en encaisse », la haute disponibilité à « comment ne pas tomber quand un composant meurt ». Elles se recoupent souvent et ne se déduisent jamais l'une de l'autre.
Les 5 niveaux que nous déployons
Classés par finesse de décision — de « le client tire au sort » à « le proxy lit la requête SQL et décide où l'envoyer ». Chaque marche gagne en précision et coûte en exploitation.
Deux axes à ne pas confondre. Le niveau de décision sur le trafic — sur quoi le répartiteur se fonde pour choisir une cible — et la profondeur du test de santé. Ils varient indépendamment : keepalived sait tester une cible en HTTP, en HTTPS ou par script alors que la répartition reste en couche 4 dans le noyau. Ce n'est donc pas pour obtenir un test applicatif qu'on passe à HAProxy — c'est pour décider requête par requête, ce que la couche 4 ne peut pas faire puisqu'elle n'ouvre jamais la connexion.
| Niveau | Qui décide | Composant central ? | Test de santé |
|---|---|---|---|
| 1 · RR DNS + plusieurs VIP | Le résolveur et le client | Aucun | Aucun côté DNS |
| 2 · Anycast | La table de routage | Aucun | Aucun côté réseau |
| 3 · IPVS[2] / LVS (couche 4) | Le noyau, sur la connexion | Oui, à redonder | Assuré par keepalived : TCP, HTTP ou script |
| 4 · HAProxy (mode HTTP, couche 7) | Le proxy, sur la requête | Oui, à redonder | Requête applicative réelle |
| 5 · Routage SQL | Le proxy, sur la requête et l'état de la réplication | Oui, à redonder | Retard de réplication |
1. Round-robin DNS et plusieurs VIP
Au lieu d'une adresse flottante unique, on en déclare deux ou trois — priorités croisées, chaque nœud portant la sienne en fonctionnement nominal — et on les publie ensemble dans un même enregistrement DNS. Le DNS fournit alors plusieurs adresses ; c'est le comportement du résolveur puis celui du client qui déterminent laquelle est essayée — certains parcourent la liste, d'autres s'en tiennent à la première.
Ce que ça a d'unique : aucun composant central, donc aucun nouveau point de panne, et si un nœud tombe sa VIP migre chez un survivant. Les limites sont connues — le DNS ne teste rien, le cache client retarde toute correction, la répartition n'est pas pondérée. Le montage est détaillé au niveau 2 de la haute disponibilité.
2. Anycast
Une même adresse annoncée depuis plusieurs endroits : chaque client est servi par l'instance la plus proche[3] au sens du routage, ce qui répartit naturellement par zone et réduit la latence. Sans configuration côté client, et là encore sans composant central.
Le piège : la répartition n'est pas pondérée et le réseau ne teste rien. Une instance qui annonce sans répondre devient un trou noir silencieux — voir anycast interne.
3. IPVS / LVS — couche 4
La répartition se fait dans le noyau, sur la connexion, sans jamais l'ouvrir. On gagne la pondération — un gros serveur reçoit plus qu'un petit. Les performances sont excellentes parce qu'il n'y a rien à interpréter. IPVS distribue seulement : c'est keepalived qui teste les cibles et retire de la table celles qui échouent.
Bonne nouvelle pour l'exploitation : c'est le même keepalived qui pilote IPVS et qui porte l'adresse flottante. On n'ajoute pas un outil, on active l'autre moitié de celui qu'on exploite déjà.
4. HAProxy — couche 7
Le proxy lit la requête. Il peut donc router selon l'URL ou l'en-tête, gérer des sessions collantes, terminer le chiffrement, et surtout tester réellement la santé applicative — pas seulement vérifier qu'un port répond.
C'est la marche où l'on gagne le plus en finesse et où l'on paie le plus en exploitation : une configuration qui vit, des certificats à renouveler, et un composant supplémentaire à redonder.
5. Les bases de données : router, pas répartir
C'est le niveau à part, et l'erreur classique est de le traiter comme les autres. Devant une base, le problème n'est pas de distribuer équitablement : c'est de savoir où chaque requête a le droit d'aller. Les écritures doivent atteindre le nœud qui fait autorité ; les lectures peuvent aller ailleurs, mais pas n'importe où.
Mais avant de conclure qu'il faut un proxy spécialisé, il y a une question à poser : qui sépare les lectures des écritures ?
L'application le fait déjà
Beaucoup d'applications utilisent deux connexions distinctes, une pour écrire et une pour lire. Dans ce cas, il n'y a plus rien à interpréter : le flux de lecture est déjà identifié, il suffit de le distribuer. HAProxy en mode TCP le fait très bien — et IPVS aussi, avec encore moins de pièces.
Personne ne le fait
Une seule chaîne de connexion, tout passe par le même canal. Il faut alors un proxy qui
parle le protocole de la base et décide requête par requête : un
répartiteur générique voit des octets, pas la différence entre un
SELECT et un
UPDATE. C'est le domaine de ProxySQL[4].
Et la lecture du verbe SQL n'est que la partie facile. Un proxy de ce type doit aussi épingler ce qui ne peut pas changer de nœud en cours de route :
- • une transaction ouverte, qui doit rester sur le même nœud jusqu'au
COMMIT— y compris ses lectures ; - • la lecture qui suit une écriture dans la même session, sous peine de lire un réplica qui n'a pas encore reçu la donnée ;
- • l'état de session : variables utilisateur, tables temporaires, requêtes préparées, transaction en cours ;
- • les lectures qui doivent impérativement atteindre le primaire, à marquer explicitement.
Notre retour d'expérience : un flux de lecture pur, réparti en couche 4
Nous avons déjà réparti un flux de lecture strictement en lecture seule avec IPVS, sans aucun proxy SQL[5]. Parce que la séparation était déjà faite en amont, il ne restait qu'un problème de distribution — et le niveau 3 le résout avec un outil qu'on exploite déjà. C'est exactement l'illustration de la règle du haut de cette page : on commence aussi bas que le besoin le permet, et on ne monte d'un niveau que lorsqu'on a besoin de ce que ce niveau apporte en propre.
Le vrai risque n'est pas la performance
C'est d'envoyer une lecture vers un réplica en retard. L'application reçoit une réponse rapide, parfaitement formée, et périmée — un enregistrement qui vient d'être créé et qui « n'existe pas encore », un solde qui n'a pas bougé. Rien n'échoue, rien n'alerte, et le bogue remonte des jours plus tard par le support. C'est pour cela qu'un proxy sérieux surveille le retard de réplication et retire de la rotation les réplicas qui décrochent : la répartition des lectures ne se décide pas sur la charge, elle se décide sur la fraîcheur.
ProxySQL
Un proxy MySQL et MariaDB qui route selon des règles portant sur les requêtes elles-mêmes, mutualise les connexions et sait écarter un réplica en retard. C'est ce que nous déployons quand la séparation doit être transparente pour l'application.
MaxScale
Le proxy de MariaDB, de la même famille de fonctions. Nous ne l'avons pas testé en production et nous préférons l'écrire : si votre contexte l'impose, nous commencerons par le qualifier avant de nous engager sur quoi que ce soit.
La couche qui décide vraiment reste toutefois la réplication elle-même : c'est elle qui détermine ce qui est routable et à quelle fraîcheur. Le sujet est traité sur notre site dédié — cluster Galera pour la réplication synchrone multi-maître, et replication-manager pour l'orchestration de la bascule.
Les sessions collantes, et pourquoi nous essayons de les éviter
Une session collante oriente un même client vers la même instance — tant que le cookie posé par le répartiteur reste valide et que cette instance est encore disponible. Ce n'est donc pas une garantie : si l'instance tombe, la session est perdue de toute façon. Elle sauve les applications qui gardent l'état de la session en mémoire locale — et beaucoup d'applications métier le font.
Ce que ça coûte
- • La répartition devient inégale : une instance peut accumuler les sessions longues
- • La panne d'une instance déconnecte tous les utilisateurs qui lui étaient attachés, alors que le service reste debout
- • La mise à jour d'un nœud demande d'attendre que ses sessions expirent, ou de les sacrifier
Ce que nous proposons d'abord
Sortir l'état de l'application — en base ou dans un cache partagé — pour que n'importe quelle instance puisse servir n'importe quel utilisateur. C'est un travail applicatif, parfois modeste, et il supprime le problème au lieu de le contourner. Quand ce n'est pas possible, on met du collant, mais en sachant ce qu'on achète.
Comment nous choisissons
Deux ou trois nœuds, trafic modéré
Niveau 1. Le round-robin DNS sur plusieurs VIP suffit, n'ajoute aucun point de panne et se maintient tout seul. Descendre plus bas n'existe pas ; monter plus haut coûte sans servir.
Front web, plusieurs applications
Niveau 4. Dès qu'il faut router selon l'URL, terminer le chiffrement à un endroit unique ou tester la santé applicative pour de vrai, HAProxy s'impose — et son propre doublon avec lui.
Base saturée en lecture
Niveau 5 seulement si l'application ne sépare pas déjà ses connexions — sinon niveau 3 ou 4 suffisent. Et dans tous les cas après avoir vérifié la réplication : router des lectures vers des réplicas n'a de sens que si l'on sait à quel point ils sont à jour.
Ce que nous exploitons, et ce que ça coûte
Un répartiteur se surveille autrement qu'un serveur. Ce qui compte n'est pas seulement qu'il réponde, c'est qu'il ait encore quelque chose vers quoi envoyer : un répartiteur en parfaite santé devant deux serveurs morts répond parfaitement des erreurs. Nous surveillons donc le nombre de cibles vivantes, la répartition réelle entre elles, et l'écart entre ce que la configuration prévoit et ce qui se passe.
Conception et déploiement
Sur devis, base 150 € HT de l'heure. Le périmètre dépend du niveau retenu, de l'applicatif et de ce que permet votre hébergeur en matière d'adresses déplaçables.
Exploitation
À partir de 150 € HT/mois par serveur, dégressif dès le deuxième nœud d'un même ensemble. Voir les formules.
Questions fréquentes sur la répartition de charge
Quelle différence entre répartition de charge et haute disponibilité ?
La haute disponibilité répond à « comment ne pas tomber quand un composant meurt ». La répartition de charge répond à « comment servir plus de trafic que n'en encaisse une machine ». Les deux se recoupent souvent — répartir sur plusieurs instances rend accessoirement la panne de l'une supportable — mais elles ne se déduisent pas l'une de l'autre. Un répartiteur unique devant deux serveurs améliore la capacité et n'améliore pas la disponibilité : il déplace simplement le point de panne devant.
Faut-il redonder le répartiteur lui-même ?
Oui, dès qu'il est le seul chemin vers le service, et c'est le paradoxe central du sujet : en ajoutant un composant pour améliorer la disponibilité, on en crée un nouveau dont dépend tout le reste. La réponse habituelle est de mettre deux répartiteurs derrière une adresse flottante, ce qui ramène au niveau 2 de nos architectures de haute disponibilité. Seuls les montages sans composant central — round-robin DNS et anycast — échappent à cette obligation, et c'est précisément ce qui fait leur intérêt.
Couche 4 ou couche 7 : comment choisir ?
La question est de savoir si les décisions de répartition doivent tenir compte du contenu des requêtes. En couche 4, on répartit des connexions sans les ouvrir : très performant, très peu de charge, et applicable à n'importe quel protocole. En couche 7, on lit la requête, ce qui permet de router selon l'URL, de gérer des sessions collantes et de tester réellement la santé applicative — au prix d'un traitement plus lourd et d'une limitation aux protocoles compris par le répartiteur.
Le round-robin DNS suffit-il ?
Pour deux ou trois nœuds, du trafic interne ou modéré, un service idempotent et sans session collante, oui — et il a un avantage que rien d'autre n'offre : aucun composant central, donc aucun nouveau point de panne. Ses limites sont connues : le DNS ne teste rien, le cache client retarde toute correction, la répartition n'est pas pondérée, et certains résolveurs ne font pas de rotation. C'est souvent la bonne première étape, rarement la dernière.
Comment répartit-on la charge d'une base de données ?
Pas comme le reste, parce que le problème n'est pas de répartir mais de router. Les écritures
doivent aller au nœud qui fait autorité, les lectures peuvent aller ailleurs — et un répartiteur
générique ne sait pas distinguer un SELECT d'un
UPDATE. Il faut un proxy qui parle le protocole de la
base, comme ProxySQL. Et le vrai risque n'est pas la performance : c'est d'envoyer une lecture
vers un réplica en retard et de servir des données périmées sans que rien ne l'indique.
Les sessions collantes, comment ça marche et faut-il en faire ?
Une session collante oriente un même client vers la même instance, tant que le cookie posé par le répartiteur reste valide et que cette instance est encore disponible. Ce n'est pas une garantie : si l'instance tombe, la session est perdue de toute façon. Cela résout le cas des applications qui gardent l'état de la session en mémoire locale. Mais c'est un pansement : la répartition devient inégale, et la panne d'une instance déconnecte tous les utilisateurs qui lui étaient attachés. Quand c'est possible, il vaut mieux sortir l'état de l'application — en base ou dans un cache partagé — et se passer de collant.