Défaillances en cascade et état distribué critique
Empêcher et endiguer les défaillances en cascade, et gérer l'état critique partagé par le consensus distribué.
Deux dangers guettent tout système distribué de grande taille : il peut s'effondrer sur lui-même sous l'effet de sa propre charge, et il peut perdre la cohérence de son état partagé entre des machines géographiquement éloignées. Ce chapitre traite ces deux faces de la fiabilité — d'un côté la défaillance en cascade (cascading failure), qui transforme une panne locale en panne globale par rétroaction positive ; de l'autre, le consensus distribué (distributed consensus), seule réponse formellement correcte au problème de maintenir un état critique cohérent malgré les pannes. Les deux sujets se rejoignent : un système de consensus mal réparti peut lui-même déclencher une cascade, et un système surchargé peut compromettre l'état qu'il prétend protéger.
Anatomie d'une défaillance en cascade
Une défaillance en cascade est une panne qui croît dans le temps sous l'effet d'une rétroaction positive (positive feedback). Une portion du système tombe, ce qui augmente la probabilité que d'autres portions tombent à leur tour. L'exemple canonique : une réplique d'un service cède sous la surcharge, sa charge se reporte sur les répliques restantes, qui saturent à leur tour — un effet domino qui finit par emporter toutes les répliques du service.
charge nominale une réplique tombe
┌────┐ ┌────┐ ┌────┐ ┌────┐ ┌────┐ ┌──╳─┐
│ R1 │ │ R2 │ │ R3 │ ───► │ R1 │ │ R2 │ │ R3 │
└────┘ └────┘ └────┘ └────┘ └────┘ └────┘
chacune à 70 % R1+R2 reçoivent +50 % de charge
│
▼
┌──╳─┐ ┌──╳─┐ ┌──╳─┐ ◄─── R1, R2 saturent puis crashent ;
│ R1 │ │ R2 │ │ R3 │ au redémarrage elles sont aussitôt
└────┘ └────┘ └────┘ bombardées et retombent (crash-loop)
tout le service est mort — rétroaction positive L'illustration la plus parlante est celle de l'équilibrage entre clusters — celle que le livre déroule avec son service de recherche dans l'œuvre de Shakespeare (Shakespeare search service), exemple-fil de tout le chapitre. Supposons un frontend qui sert 1 000 requêtes par seconde (QPS) dans le cluster A, le reste du trafic étant absorbé par le cluster B. Si B tombe, le contrôleur d'équilibrage redirige son trafic vers A, qui passe à 1 200 QPS — au-delà de sa capacité. A se met à manquer de ressources : ses tâches plantent, ratent leurs échéances, et son taux de requêtes réellement servies chute bien en dessous de 1 000 QPS. La surcharge locale peut alors se propager : le contrôleur déplace de nouveau la charge, contaminant d'autres clusters jusqu'à une panne globale. Ces enchaînements se jouent parfois en quelques minutes, car équilibreurs et ordonnanceurs réagissent très vite.
Les causes : surcharge, épuisement de ressources, indisponibilité
La cause la plus fréquente est la surcharge serveur (server overload) ; presque toutes les cascades en sont une variante. Vient ensuite l'épuisement de ressources (resource exhaustion), dont les effets diffèrent selon la ressource saturée.
| Ressource épuisée | Effets typiques |
|---|---|
| CPU | Toutes les requêtes ralentissent ; plus de requêtes en vol, files d'attente trop longues, famine de threads, échéances RPC manquées, moins de bénéfice des caches CPU |
| Mémoire | Tâches tuées par le gestionnaire de conteneurs ; en Java, la « spirale de la mort du GC » (GC death spiral) — moins de CPU → requêtes plus lentes → plus de RAM → plus de GC → encore moins de CPU |
| Threads | Famine de threads, erreurs directes ou échecs des contrôles de santé ; à l'extrême, épuisement des identifiants de processus |
| Descripteurs de fichiers | Impossibilité d'ouvrir des connexions réseau, d'où échec des contrôles de santé |
Le piège tient à ce que ces phénomènes se nourrissent les uns les autres. Un frontend Java mal réglé épuise son CPU sous une charge pourtant attendue ; le ralentissement gonfle la RAM consommée par les requêtes en cours ; il reste moins de RAM pour le cache, dont le taux de succès chute ; davantage de requêtes percent jusqu'au backend, qui épuise à son tour CPU et threads ; ses contrôles de santé échouent — la cascade démarre. Dans un tel enchevêtrement, il est presque impossible de diagnostiquer la chaîne causale complète pendant l'incident, surtout si frontend et backend ont des propriétaires distincts.
La troisième cause est l'indisponibilité de service (service unavailability). Une fois quelques serveurs crashés, la charge sur les survivants augmente, qui crashent à leur tour : le problème fait boule de neige et l'ensemble entre en crash-loop. Réduire la charge ne suffit pas à sortir de cet état. Si un service sain à 10 000 QPS a basculé en cascade à 11 000 QPS, redescendre à 9 000 QPS n'arrêtera presque jamais les crashs, car le système affronte une demande accrue avec une capacité réduite : seule une fraction des serveurs est assez saine pour servir. Si 10 % seulement des serveurs tiennent, il faudrait retomber à environ 1 000 QPS pour que le système se stabilise.
Attention
Les politiques d'équilibrage qui évitent les serveurs ayant renvoyé des erreurs aggravent souvent la cascade : quelques backends renvoient des erreurs, on cesse de leur envoyer du trafic, ce qui reporte leur charge sur les survivants et amorce l'effet boule de neige. Ce qui protège dans le cas normal peut précipiter la panne globale.
Concevoir pour éviter la cascade
La parade s'organise par priorité décroissante. La mesure la plus importante est de tester la capacité du serveur jusqu'à la rupture : tant qu'on n'a pas observé, en environnement réaliste, quelle ressource cède en premier et comment, on ne peut pas prévoir le mode de défaillance.
| Contre-mesure | Principe |
|---|---|
| Test de charge jusqu'à la rupture | Mesurer le point de rupture et le comportement au-delà ; fonde la planification de capacité |
| Résultats dégradés (serve degraded results) | Renvoyer des résultats moins riches mais moins coûteux à calculer |
| Rejet précoce (reject when overloaded) | Le serveur se protège : échouer tôt et à bas coût (ex. HTTP 503) plutôt que crasher |
| Limitation de débit en amont (rate limiting) | Aux reverse proxies (par IP), aux équilibreurs (sélectif ou indiscriminé), à chaque tâche |
| Planification de capacité (capacity planning) | Provisionner en N + 2 selon le point de rupture mesuré ; couplée aux tests de performance |
| Gestion des files (queue management) | Files courtes (≤ 50 % du pool de threads) pour rejeter tôt ; CoDel ou LIFO pour purger les requêtes périmées |
Délestage et dégradation gracieuse
Le délestage (load shedding) consiste à abandonner une proportion du trafic à l'approche de la surcharge, pour éviter de manquer de RAM, de rater les contrôles de santé ou de servir avec une latence extrême — tout en accomplissant le maximum de travail utile. Une approche efficace : renvoyer un HTTP 503 dès qu'un nombre donné de requêtes clientes sont en vol. Plus subtil encore, basculer la file du classique premier-entré-premier-sorti (FIFO) vers dernier-entré-premier-sorti (LIFO), ou employer l'algorithme CoDel (controlled delay) : si une requête de recherche a mijoté dix secondes dans la file, l'utilisateur a probablement déjà rafraîchi sa page — répondre à la première requête ne sert plus à rien.
La dégradation gracieuse (graceful degradation) va plus loin en réduisant la quantité de travail : une recherche peut n'interroger qu'un sous-ensemble en cache plutôt que la base sur disque, ou recourir à un algorithme de classement moins précis mais plus rapide. Attention toutefois : ce mode ne se déclenche que rarement, donc le chemin de code qu'on n'emprunte jamais est celui qui (souvent) ne fonctionne pas. Pour le garder opérationnel, on fait régulièrement tourner quelques serveurs près de la surcharge afin d'exercer ce code. Il faut aussi surveiller et alerter quand trop de serveurs entrent en mode dégradé, et prévoir un moyen de couper rapidement une dégradation complexe qui s'emballerait.
Les retries : amplificateur de cascade
Les réessais (retries) figurent parmi les déclencheurs les plus sournois. Imaginons un backend limité à 10 000 QPS ; le frontend l'appelle à 10 100 QPS. Les 100 QPS rejetés sont réessayés, s'ajoutant au trafic : le backend reçoit alors 10 200 QPS, dont 200 échouent. Le volume de réessais croît — 100, puis 200, puis 300 QPS — et de moins en moins de requêtes réussissent du premier coup. Le backend finit par fondre sous le poids combiné des requêtes et de leurs réessais. Pire : même en redescendant à 9 000 QPS, si traiter une requête vouée à l'échec coûte des ressources, les réessais peuvent maintenir le backend en surcharge.
À retenir
Toujours espacer les réessais par un backoff exponentiel aléatoire (randomized exponential backoff) — c'est-à-dire backoff exponentiel plus jitter. Sans cette part d'aléa, une simple perturbation réseau peut resynchroniser tous les réessais sur le même instant, créant des « vagues » qui s'auto-amplifient. Comme le résume un ingénieur de Google : « si du premier coup tu échoues, recule exponentiellement » ; et un autre d'ajouter : « pourquoi les gens oublient-ils toujours d'ajouter un peu de jitter ? »
Au-delà du backoff, on limite le nombre de réessais par requête, on instaure un budget de réessais par processus (par exemple 60 réessais par minute, au-delà duquel on échoue directement), et l'on évite surtout d'empiler les réessais à plusieurs niveaux : si la base est débordée et que les trois couches au-dessus d'elle — le backend, le frontend et le JavaScript client — tentent chacune 4 essais, une seule action utilisateur peut générer 4³ = 64 tentatives sur la base. Enfin, on distingue clairement les erreurs réessayables des erreurs permanentes, et l'on renvoie un statut spécifique « surchargé » pour que les couches supérieures sachent reculer.
Échéances, propagation et latence bimodale
Quand un frontend envoie un RPC, il consomme des ressources en attendant la réponse. L'échéance (deadline) limite cette attente. Le thème récurrent des cascades : des serveurs dépensent des ressources à traiter des requêtes dont l'échéance côté client est déjà expirée — « on ne reçoit aucun crédit pour les copies rendues en retard ». Si un RPC a une échéance de 10 secondes mais qu'il faut 11 secondes pour passer de la file au thread, le serveur ne devrait même pas tenter de le traiter.
La bonne pratique est la propagation d'échéance (deadline propagation) : l'échéance est fixée haut dans la pile et décompte le long de l'arbre d'appels. Si A choisit 30 secondes et travaille 7 secondes avant d'appeler B, le RPC vers B porte une échéance de 23 secondes ; si B travaille 4 secondes avant d'appeler C, l'échéance vers C devient 19 secondes. Sans cette propagation, un serveur profond peut travailler pour une requête abandonnée depuis longtemps tout en haut. On y adjoint la propagation d'annulation (cancellation propagation) pour éviter les fuites de RPC.
Piège courant
Méfiez-vous de la latence bimodale. Supposons 10 serveurs de 100 threads — soit 1 000 threads, dont 100 occupés en régime normal (1 000 QPS × 0,1 s). Si un incident fait que 5 % des requêtes n'aboutissent jamais et heurtent une échéance de 100 secondes, ces 5 % consomment 5 000 threads — bien plus que les 1 000 disponibles. Résultat : la majorité des requêtes échouent, soit un taux d'erreur de 80 %, là où seulement 5 % auraient dû échouer. Des échéances de plusieurs ordres de grandeur supérieures à la latence moyenne sont presque toujours néfastes ; observez la distribution des latences, pas seulement la moyenne, et utilisez le fail-fast quand un backend est indisponible.
Démarrage lent, caches froids, et le sens de la pile
Un processus fraîchement démarré est plus lent : initialisation des connexions, compilation JIT en Java, caches vides. Un cache froid (cold cache) est dangereux quand le service ne survit pas sans lui. On distingue le cache de latence — le service tient sa charge avec un cache vide, le cache n'accélère que les réponses — du cache de capacité — le service ne tient pas sa charge sans cache. Le second est en réalité une dépendance dure déguisée. Parades : surprovisionner, exercer les protections anti-surcharge, et monter la charge progressivement sur un nouveau cluster pour réchauffer les caches avant d'ouvrir les vannes.
Enfin, toujours descendre dans la pile : éviter la communication intra-couche (un backend qui en appelle un autre) dans le chemin des requêtes, car elle expose à l'interblocage distribué (distributed deadlock) — le pool de threads de A est plein, B attend A en occupant un thread de B, la saturation se propage. Si un frontend se trompe de backend, ce dernier ne doit pas faire suivre la requête : il doit dire au frontend de réessayer vers le bon backend.
Pris dans la cascade : que faire ?
Une cascade est une bonne occasion d'enclencher le protocole de gestion d'incident (IMAG). Plusieurs leviers, à combiner avec prudence :
- Augmenter les ressources si l'on dispose de capacité inactive — inutile toutefois si le système est déjà en spirale de la mort.
- Stopper les échecs de contrôle de santé : si l'ordonnanceur (comme Borg) tue et redémarre sans cesse des tâches surchargées, désactiver temporairement les contrôles de santé peut laisser le système se stabiliser. On distingue le contrôle de processus (« le binaire répond-il ? », pertinent pour l'ordonnanceur) du contrôle de service (« peut-il servir cette classe de requêtes ? », pertinent pour l'équilibreur).
- Redémarrer les serveurs coincés (spirale de GC, threads bloqués, interblocage) — mais en canary, lentement, après avoir identifié la cause, sous peine d'amplifier la panne.
- Couper le trafic : le gros marteau. Si tout est en crash-loop, ne laisser passer que 1 % du trafic, laisser les serveurs redevenir sains, puis remonter graduellement — non sans avoir d'abord corrigé la condition déclenchante, faute de quoi la cascade reviendra dès le retour du trafic.
- Entrer en mode dégradé, éliminer la charge par lots (index, copies, statistiques) et éliminer le mauvais trafic.
Note
La requête de la mort (Query of Death) est un RPC dont le contenu déclenche une défaillance dans le processus qui le traite. Quelques-unes suffisent à faire chuter un service au bord de la rupture : repérez et bloquez ces requêtes empoisonnées. Plus largement, méfiez-vous des changements bien intentionnés — réessais, déplacement de charge, suppression des serveurs malades, ajout de caches — qui améliorent le cas normal mais accroissent le risque d'une panne massive. Veillez à ne pas échanger une panne contre une autre.
Note
Une cascade racontée : le service Shakespeare. Le livre clôt le chapitre sur un récit qui met en scène, bout à bout, presque tous les leviers décrits. Un documentaire consacré à l'œuvre de Shakespeare est diffusé au Japon et désigne explicitement le service de recherche comme l'endroit idéal pour pousser l'enquête. À la suite de l'émission, le trafic vers le centre de données asiatique enfle bien au-delà de la capacité prévue, problème encore aggravé par une mise à jour majeure du service déployée au même moment dans ce même centre. Plusieurs garde-fous limitent les dégâts : la revue de mise en production (Production Readiness Review, PRR) avait fait corriger certains points en amont, et les développeurs avaient bâti une dégradation gracieuse — quand la capacité se raréfie, le service cesse de renvoyer les images et les petites cartes accompagnant le texte, et un RPC qui dépasse son échéance est soit abandonné (pour les images, par exemple), soit réessayé avec un backoff exponentiel aléatoire. Malgré tout, les tâches tombent une à une et sont aussitôt redémarrées par Borg, ce qui fait encore décroître le nombre de tâches valides. Des courbes du tableau de bord virent à un rouge inquiétant et le SRE est paginé ; en réponse, l'équipe ajoute temporairement de la capacité au centre asiatique en augmentant le nombre de tâches du service, ce qui suffit à le rétablir. Suit un postmortem détaillant l'enchaînement, ce qui a bien marché, ce qui aurait pu mieux se passer, et des actions correctives : faire désormais rediriger une partie du trafic vers les centres voisins par le GSLB en cas de surcharge, et activer l'autoscaling pour que le nombre de tâches croisse automatiquement avec le trafic.
Gérer l'état critique : le consensus distribué
Les processus plantent, les disques cèdent, une catastrophe peut emporter plusieurs centres de données d'une région. La survie impose de répartir les systèmes sur plusieurs sites — ce qui est facile — mais alors d'y maintenir une vue cohérente de l'état, ce qui est autrement délicat. Un groupe de processus doit pouvoir se mettre d'accord, de façon fiable, sur des questions comme : qui est le leader ? quel est l'ensemble des membres du groupe ? un message a-t-il bien été validé dans une file ? un verrou est-il détenu ? quelle est la valeur associée à une clé ?
Le problème du consensus distribué consiste à atteindre l'accord entre des processus reliés par un réseau peu fiable. C'est l'un des concepts les plus fondamentaux de l'informatique distribuée. Dès que vous voyez de l'élection de leader, de l'état partagé critique ou du verrouillage distribué, employez un système de consensus formellement prouvé et abondamment testé. Les approches improvisées — battements de cœur (heartbeats), protocoles de rumeur (gossip) — provoquent des pannes et, plus insidieusement, des corruptions de données difficiles à corriger.
À retenir
Le théorème CAP établit qu'un système distribué ne peut garantir simultanément les trois propriétés suivantes : cohérence (Consistency) des vues à chaque nœud, disponibilité (Availability) à chaque nœud, et tolérance aux partitions (Partition tolerance) du réseau. La logique est intuitive : si deux nœuds ne peuvent plus communiquer, le système doit choisir entre cesser de servir (perte de disponibilité) ou continuer (vues incohérentes). Comme les partitions réseau sont inévitables, comprendre le consensus revient à comprendre comment arbitrer cohérence et disponibilité pour son application.
Beaucoup d'équipes optent pour les sémantiques BASE (Basically Available, Soft state, Eventual consistency) plutôt qu'ACID, s'appuyant sur la réplication multimaître et la « cohérence à terme » (eventual consistency) — souvent résolue par un naïf « le dernier horodatage gagne ». Mais la cohérence à terme réserve de mauvaises surprises, notamment en cas de dérive d'horloge (inévitable) ou de partition. Jeff Shute le résume : « les développeurs passent une part considérable de leur temps à bâtir des mécanismes complexes et fragiles pour composer avec la cohérence à terme [...] ces problèmes devraient être résolus au niveau de la base de données ». On ne peut sacrifier la correction au nom de la fiabilité ou de la performance, surtout pour l'état critique — songez à un système de transactions financières.
Trois échecs typiques résolus par le consensus
Trois études de cas réelles montrent que d'innombrables problèmes ne sont que des variantes du consensus distribué.
| Cas | Le problème | La leçon |
|---|---|---|
| Cerveau dédoublé (split-brain) | Deux serveurs de fichiers répliqués s'envoient des commandes STONITH (« Shoot The Other Node In The Head ») sur échéance de heartbeat ; un réseau lent fait que les deux se croient maîtres, d'où corruption | L'élection de leader ne se résout pas par de simples timeouts |
| Bascule manuelle (failover) | Un primaire devenu injoignable de son secondaire se rend indisponible et escalade vers un humain, pour éviter le split-brain | Pénalise la disponibilité et charge inutilement les ingénieurs ; un humain n'élit pas mieux un maître qu'un algorithme quand le réseau est coupé |
| Appartenance au groupe défaillante | Des nœuds se découvrent par gossip et élisent un leader ; une partition fait élire deux maîtres, chacun acceptant écritures et suppressions → corruption | Déterminer une vue cohérente de l'appartenance est, encore, un problème de consensus |
Comment fonctionne le consensus
On s'intéresse au consensus distribué asynchrone, valable pour des réseaux à délais potentiellement non bornés. Les algorithmes peuvent être crash-fail (un nœud planté ne revient jamais) ou crash-recover — ces derniers, bien plus utiles, car la plupart des pannes réelles sont transitoires. Ils gèrent les pannes byzantines (messages incorrects par bug ou malveillance, coûteuses, rares) ou non byzantines.
Fait remarquable : résoudre le consensus asynchrone en temps borné est impossible — c'est le résultat d'impossibilité FLP, primé par le prix Dijkstra : aucun algorithme ne peut garantir la progression en présence d'un réseau non fiable. En pratique, on s'en approche en s'assurant d'assez de répliques saines et de connectivité pour progresser la plupart du temps, et en ajoutant des backoffs aléatoires. Les protocoles garantissent la sûreté (safety) ; la redondance encourage la vivacité (liveness).
La première solution fut le protocole Paxos de Lamport ; d'autres existent : Raft, Zab, Mencius. Paxos opère comme une suite de propositions, chacune dotée d'un numéro de séquence imposant un ordre strict. Dans la première phase, le proposeur envoie un numéro aux accepteurs ; chacun n'accepte que s'il n'a pas déjà vu de numéro supérieur. S'il obtient l'accord d'une majorité, le proposeur valide en envoyant la valeur. Deux majorités se recoupant toujours sur au moins un nœud, deux valeurs différentes ne peuvent être validées pour une même proposition. Les accepteurs doivent journaliser sur stockage persistant chaque accord, pour honorer leurs engagements après un redémarrage.
Patrons d'architecture
Les algorithmes de consensus sont bas niveau : ils ne savent qu'agréer sur une valeur, une fois. Leur utilité vient des composants de plus haut niveau bâtis au-dessus. Beaucoup de systèmes les consomment comme un service : Zookeeper (le premier open source à percer), Consul, etcd. Chez Google, Chubby remplit ce rôle ; fournir des primitives de consensus en tant que service, plutôt que comme bibliothèque, libère les équipes du fardeau de déployer correctement un service de consensus hautement disponible.
Couche applicative
┌──────────────────────────────────────────┐
│ Datastores · Files · Verrous · Élection │ ← composants utiles
├──────────────────────────────────────────┤
│ Machine à états répliquée (RSM) │ ← exécute les opérations
│ │ dans l'ordre convenu
├──────────────────────────────────────────┤
│ Algorithme de consensus (Paxos / Raft) │ ← accord sur la SÉQUENCE
└──────────────────────────────────────────┘ La brique fondatrice est la machine à états répliquée (replicated state machine, RSM) : un système qui exécute le même ensemble d'opérations, dans le même ordre, sur plusieurs processus. Le consensus s'accorde sur la séquence ; la RSM exécute. Concept puissant : tout programme déterministe peut devenir un service hautement disponible en l'implémentant comme une RSM. De là découlent les datastores et magasins de configuration répliqués (le consensus est dans leur chemin critique, d'où l'importance du débit), l'élection de leader hautement disponible (équivalente au consensus, elle assure l'exclusion mutuelle ; le consensus n'est alors pas dans le chemin critique, comme dans GFS ou Bigtable), les barrières et verrous (toujours avec des baux renouvelables à expiration plutôt que des verrous indéfinis), et les files et messageries fiables (la diffusion atomique étant prouvée équivalente au consensus).
Fiabilité, performance et déploiement
La sagesse populaire veut le consensus trop lent ; c'est faux, malgré des implémentations parfois poussives. Multi-Paxos élit un leader stable : une fois la vue (ou « terme ») établie, un seul aller-retour vers un quorum d'accepteurs suffit. Changer de proposeur coûte un aller-retour supplémentaire et risque surtout les proposeurs en duel (dueling proposers) — un livelock où les propositions s'interrompent mutuellement à l'infini. La parade : un proposeur unique élu, ou un proposeur tournant, et toujours de l'aléa dans les timeouts d'élection (Raft excelle sur ce point).
La performance se heurte à deux contraintes physiques : le temps d'aller-retour réseau (environ 1 ms intra-centre, 45 ms à travers les États-Unis, 70 ms de New York à Londres) et l'écriture sur stockage persistant (1 à plusieurs ms). Une opération de consensus exige une écriture disque chez le proposeur, des messages parallèles aux accepteurs, leurs écritures disque parallèles, puis les réponses. Pour les charges en lecture lourde, on lit depuis n'importe quelle réplique si la cohérence forte n'est pas requise, ou l'on emploie les baux de quorum (quorum leases) pour des lectures locales fortement cohérentes. Le batching et le pipelining amortissent les coûts fixes.
Astuce
Pour le nombre de répliques : un quorum majoritaire de 2f + 1 répliques tolère f pannes (3f + 1 pour le byzantin). Trois répliques tolèrent une panne — assez pour la maintenance planifiée, mais une panne imprévue pendant la maintenance rend alors le système indisponible. Cinq répliques sont donc la norme (Zookeeper, Chubby) : elles tolèrent deux pannes. Attention au piège : ajouter une sixième réplique peut réduire la disponibilité (le quorum passe de 3 à 4), et colocaliser deux répliques dans un même centre de données peut faire perdre toute redondance lors de sa panne. Quant à l'emplacement, on arbitre entre domaines de défaillance (machine, baie, centre, région) et latence — sans jamais oublier que deux domaines restent inéluctables : le logiciel lui-même et l'erreur humaine. D'où la nécessité de sauvegardes régulières, même avec un consensus solide.
La surveillance d'un système de consensus mérite une attention propre : nombre et santé des membres, répliques en retard chronique, existence d'un leader (sans leader, un système à base de Multi-Paxos est totalement indisponible), nombre de changements de leader (un leader qui « bat de l'aile » trahit des soucis réseau), numéro de transaction de consensus (doit croître si le système est sain), ainsi que débit et latence. Enfin, gare au lien avec le chapitre précédent : router naïvement chaque lecture vers la réplique la plus proche peut, lors d'un pic régional, la submerger, puis la suivante, puis la suivante — autrement dit déclencher une défaillance en cascade au cœur même du système censé garantir la cohérence.
À retenir
- Une défaillance en cascade croît par rétroaction positive : surcharge, épuisement de ressources et indisponibilité s'auto-entretiennent ; une fois en crash-loop, réduire la charge ne suffit plus à sortir du gouffre.
- On prévient la cascade par le test jusqu'à la rupture, le délestage et la dégradation gracieuse, le rejet précoce, la limitation de débit, la planification de capacité et des files courtes — en sachant que le code de secours rarement emprunté est celui qui souvent ne marche pas.
- Les réessais amplifient toute surcharge : toujours backoff exponentiel + jitter, budget de réessais borné, jamais de réessais empilés sur plusieurs couches, et un statut « surchargé » clair pour que les couches reculent.
- Fixez des échéances et propagez-les dans l'arbre d'appels (on ne reçoit aucun crédit pour le travail rendu en retard) ; surveillez la distribution des latences pour démasquer la latence bimodale.
- Pris dans la cascade, déclenchez l'IMAG puis combinez : ajout de ressources, arrêt des contrôles de santé tueurs (Borg), redémarrage en canary, coupure progressive du trafic (1 %), modes dégradés, et neutralisation de la requête de la mort (Query of Death).
- Tout problème d'élection de leader, d'état partagé critique ou de verrou distribué est un problème de consensus distribué : n'employez jamais de heartbeats ou de gossip improvisés (split-brain, corruption) mais des algorithmes prouvés — Paxos, Raft, Zab — souvent consommés comme service (Chubby, Zookeeper, etcd).
- Le théorème CAP impose d'arbitrer cohérence et disponibilité face aux partitions inévitables ; le résultat FLP interdit la garantie en temps borné, qu'on contourne par assez de répliques saines et de l'aléa ; en pratique, déployez cinq répliques réparties sur des domaines de défaillance distincts, sans oublier les sauvegardes contre les bugs et l'erreur humaine.