Les trois concepts de défaillance à connaître
Bienvenue
Les systèmes distribués échouent selon des schémas. Une fois que vous maîtrisez ces schémas, chaque post-mortem devient un exercice de reconnaissance plutôt qu'un mystère.
Trois concepts couvrent l'essentiel de l'analyse des pannes en production :
Point de défaillance unique (SPOF) : un composant dont la défaillance entraîne la chute d'un système plus large. Souvent caché : le serveur DNS sur lequel tout le monde dépend ; le certificat contre lequel tout est renouvelé ; le maître de base de données unique.
Échec en cascade : la défaillance d'un composant en déclenche une autre, qui en déclenche une autre encore. Une base de données lente provoque des dépassements de délai (timeouts) dans la couche API, ce qui provoque des nouvelles tentatives (retries), qui surchargent davantage la base de données, ce qui provoque encore plus de dépassements de délai. L'onde de choc se propage.
Rayon d'impact : la part du système qui tombe en panne lorsqu'un composant échoue. Les choix architecturaux déterminent si ce rayon est borné ou non. Un point de défaillance unique (SPOF) a un rayon d'impact illimité. Un service cloisonné (bulkhead) a un rayon d'impact borné.
À la fin de cette leçon, vous serez en mesure de :
- Identifier les points de défaillance unique (SPOF) dans une architecture par inspection
- Reconnaître les motifs d'échec en cascade : troupeau tonnant (thundering herd), tempête de nouvelles tentatives (retry storm), file d'attente de la mort (queue of death)
- Lire une chronologie réelle et distinguer le déclencheur du défaut latent que ce déclencheur a mis en lumière
- Rédiger des actions correctives sans recherche de coupable (blameless) qui ciblent les systèmes plutôt que les personnes, couvrant la prévention / la détection / la récupération
- Raisonner sur les cloisons (bulkheads) et les disjoncteurs (circuit breakers) en tant qu'outils de limitation du rayon d'impact
Identifier le point de défaillance unique
Inspection de l'architecture en couches
Considérons une petite architecture web :
- DNS : api.example.com -> un seul IP de serveur de noms 203.0.113.10 hébergé par un seul fournisseur DNS
- CDN : un seul fournisseur de CDN devant api.example.com
- Ingress : deux machines de proxy inverse derrière un équilibreur de charge
- Backend : six répliques API dans deux zones de disponibilité (trois par zone)
- Base de données : un primaire + une réplique en lecture, dans la même zone de disponibilité
- Cache : cluster Redis, trois nœuds répartis sur les deux mêmes zones de disponibilité
Question : quels composants sont des SPOF ? Indice : les SPOF ne sont pas toujours du type « machine unique » évident. Un cluster de trois machines toutes situées dans une même zone de disponibilité constitue un SPOF en cas de défaillance de cette zone.
Trois modèles de cascade classiques
Les pannes se propagent à travers les dépendances
Modèle 1 : Essaim tonitruant (Thundering herd). Une ressource partagée (cache, verrou, base de données) tombe en panne ou redémarre. Tous les clients qui en dépendaient relancent leurs requêtes simultanément. La vague de requêtes submerge le service qui revient en ligne ; les nouvelles tentatives s'accumulent plus vite que la récupération ne peut les absorber ; la récupération n'aboutit jamais.
Modèle 2 : Tempête de réessais (Retry storm). Un service en aval ralentit. Les appelants en amont, au lieu d'échouer, relancent leurs requêtes. Ces réessais multiplient la charge initiale. Le service lent ralentit davantage, déclenchant encore plus de réessais. Finalement, la charge dépasse la capacité même d'une version saine du service.
Modèle 3 : File d'attente de la mort. Une file d'attente de traitement sans rétroaction (backpressure) reçoit des éléments plus vite qu'elle ne les traite. La file grandit sans limite. La mémoire s'épuise ; le consommateur plante ; redémarre ; découvre une file encore plus grande ; plante à nouveau.
Fil conducteur : une petite perturbation initiale déclenche une boucle de rétroaction positive. La réponse du système amplifie la panne au lieu de l'amortir.
Mécanismes d'amortissement
Backoff exponentiel avec jitter. Les clients qui réessument attendent de plus en plus longtemps à chaque tentative, avec un décalage aléatoire. Cela empêche les vagues de réessais synchronisés.
Disjoncteur (Circuit breaker). Un appelant suit le taux d'échec en aval. Au-delà d'un seuil, l'appelant cesse d'appeler pendant une période de refroidissement et échoue immédiatement ses propres requêtes à la place. Cela évite un travail inutile et permet au système aval de se rétablir.
Bulkhead (Cloison). Isoler les ressources par dépendance. Un pool de connexions A pour la base de données, un pool de connexions B séparé pour le cache. Une base de données lente ne peut pas épuiser toutes les connexions ; les appels au cache continuent.
Rejet de charge (Load shedding). En cas de surcharge, abandonner les requêtes à la périphérie plutôt que de les accepter et d'échouer lentement. Un 429 en 1 ms est préférable à un 500 en 30 secondes.
Contre-pression (Backpressure). Ralentir les producteurs lorsque les consommateurs ne peuvent pas suivre. Les files d'attente deviennent bornées ; les émetteurs sont bloqués ; la source originale du travail ressent la friction.
Diagnostiquer une cascade
L'API d'une équipe s'effondre lors d'un basculement de base de données de routine. Chronologie :
- 14:00:00 — l'opérateur promeut la base de données en attente. Indisponibilité attendue : ~10 secondes.
- 14:00:08 — le nœud principal est indisponible. Les requêtes de la couche API commencent à échouer avec des erreurs de connexion à la base de données.
- 14:00:08 — la couche API effectue des nouvelles tentatives (configuration par défaut : 5 nouvelles tentatives, sans délai exponentiel, espacées de 100 ms).
- 14:00:11 — le nœud en attente est promu et accepte les nouvelles connexions.
- 14:00:11 — la couche API ouvre des milliers de nouvelles connexions à la base de données simultanément (chaque réplique × chaque requête concurrente × chaque nouvelle tentative).
- 14:00:13 — le pool de connexions du nouveau nœud principal est épuisé ; les nouvelles connexions sont rejetées.
- 14:00:13-14:05:00 — les répliques de la couche API épuisent leurs pools de connexions, lèvent des exceptions, s'arrêtent, redémarrent, et le cycle recommence.
- 14:05:00 — l'opérateur arrête manuellement le trafic de la couche API ; la base de données se stabilise.
- 14:10:00 — La restauration progressive du trafic est terminée. Durée totale de l'interruption : ~10 minutes (contre ~10 secondes attendues).
DNS SERVFAIL : Deux défauts aggravants
Un post-mortem de forme réelle
Ce qui suit est une version assainie d'un incident réel. Les noms des fournisseurs ont été modifiés, les adresses IP ont été anonymisées ; la forme, la chronologie et les leçons sont réelles.
Résumé
Le site example.com a retourné SERVFAIL depuis tous les résolveurs DNS publics pendant environ 3 à 4 heures. Les 46 autres zones sur le même maître DNS n'ont pas été affectées. Cause racine : deux défauts aggravants.
1. Le fournisseur A (un fournisseur DNS secondaire) a ajouté une nouvelle adresse IP de synchronisation interne qui n'était pas dans la liste d'autorisation allow-axfr-ips du serveur principal.
2. La zone example.com présentait un conflit CNAME en violation de la RFC, vieux de plusieurs années (demo.example.com possédait à la fois des enregistrements CNAME et MX/TXT au même nom) qui a causé le rejet de la zone par le fournisseur A lors d'un AXFR neuf.
Chronologie (UTC)
- ~15:00 — Le fournisseur A ajoute la nouvelle IP de synchronisation 198.51.100.42 à son infrastructure
- 15:02 — première occurrence de AXFR-out denied pour 198.51.100.42 dans les journaux du DNS principal (aucune alerte sur ce signal)
- ~18:00 — Fenêtre d'expiration SOA atteinte ; le fournisseur A supprime la zone example.com de son cache
- ~18:30 — SERVFAIL détecté de manière externe
- ~19:45 — Cause racine identifiée
- 20:00 — 198.51.100.42 ajouté à allow-axfr-ips ; redémarrage du serveur principal
- 20:05 — NOTIFY envoyé ; AXFR initié ; la zone est ENCORE en SERVFAIL (conflit CNAME)
- 20:07 — check-zone révèle 1 erreur : conflit CNAME sur demo.example.com
- 20:09 — Enregistrement CNAME remplacé par un enregistrement A ; vérification de la zone propre (0 erreur)
- 20:10 — NOTIFY envoyé ; AXFR terminé ; le Fournisseur A commence à servir la zone
- 20:11 — dig @8.8.8.8 example.com A renvoie l'IP correcte — RÉSOLU
Pourquoi seulement example.com ?
Les 47 zones partagent le même DNS primaire. Le blocage IP AXFR a affecté toutes les zones. Mais seul example.com avait le conflit CNAME, & seul example.com avait besoin d'un nouvel AXFR au moment où le refus a été appliqué. Les autres zones avaient déjà été actualisées avant le refus ou n'en avaient pas encore besoin.
Défaut latent
Le conflit CNAME sur demo.example.com existait depuis des années. Il fonctionnait parce que le primaire servait la zone depuis sa base de données (tolérant les violations de la RFC) & le Fournisseur A servait à partir de données en cache obsolètes d'avant l'introduction de la violation. Lorsque le Fournisseur A a purgé son cache & a eu besoin de données fraîches, la violation est apparue.
Déclencheur
Le fournisseur A a silencieusement ajouté une nouvelle IP de synchronisation. La liste d'autorisation de l'instance principale ne l'incluait pas. Le transfert AXFR a été refusé. Trois heures plus tard (expiration du SOA), le fournisseur A a supprimé la zone. Le défaut latent est apparu lorsque le système a tenté de récupérer.
Rédiger des actions correctives sans recherche de coupable
Sans recherche de coupable : cibler les systèmes, pas les personnes
Une action corrective sans recherche de coupable désigne ce que le système devrait faire différemment, et non ce qu'une personne devrait faire différemment. « Former l'opérateur » est une recherche de coupable. « Ajouter une vérification automatisée qui détecte cela avant le déploiement » est sans recherche de coupable.
Les bonnes actions correctives sans recherche de coupable se regroupent en trois dimensions :
- Prévention : rendre le problème plus difficile ou impossible
- Détection : le détecter plus tôt s'il se produit
- Récupération : limiter les dégâts lorsqu'il se produit
Chaque élément doit préciser (1) le changement système spécifique, (2) l'équipe responsable, et (3) la dimension qu'il sert.
Des compartiments qui coulent sans le navire
Emprunté de l'ingénierie navale
Les navires disposent de cloisons étanches : des parois verticales qui divisent la coque en compartiments. Un compartiment peut se remplir d'eau sans faire couler le navire ; un autre peut échouer sans affecter le reste.
Les systèmes distribués empruntent le même mot et la même idée.
Motif des cloisons : isoler les ressources par dépendance. Un service qui appelle trois API en aval utilise trois pools de connexions distincts, trois pools de threads distincts et trois budgets de réessais distincts. Une dépendance en aval lente ou défaillante ne peut pas consommer les ressources allouées aux deux autres.
Sans cloisons : une dépendance lente épuise le pool de threads partagé ; les appels aux autres dépendances sont bloqués en attendant des threads ; le service entier devient non réactif.
Avec cloisons : une dépendance lente épuise son propre pool ; les appels à celle-ci échouent rapidement ; les appels aux autres dépendances continuent normalement ; le rayon de l'explosion reste limité à la dépendance défaillante.
Disjoncteurs
Motif du disjoncteur (Circuit breaker) : un wrapper à état autour d'une dépendance en aval qui suit le taux d'échec. Trois états :
- Fermé (normal) : les appels passent. Les échecs sont comptés.
- Ouvert (déclenché) : une fois un seuil d'échec atteint (par exemple, 50 % d'échecs au cours des 30 dernières secondes), le disjoncteur s'ouvre. Les appels échouent immédiatement sans tenter d'accéder à la dépendance. Cela évite à l'appelant de gaspiller des ressources et protège la dépendance de la charge alors qu'elle est en mauvaise santé.
- Demi-ouvert (test) : après une période de refroidissement, le disjoncteur laisse passer une petite fraction des appels. S'ils réussissent, il se referme pour revenir à la normale. S'ils échouent, il rouvre pour une nouvelle période de refroidissement.
L'insight clé : le disjoncteur empêche les efforts inutiles pendant les périodes de mauvaise santé connues et donne à la dépendance en aval une chance de récupérer sans charge continue.
Les cloisons (Bulkheads) limitent le rayon de l'explosion. Les disjoncteurs empêchent l'explosion de se maintenir.
Limiter le rayon de l'explosion
Votre service API appelle quatre services en aval : le Service Utilisateur, le Service de Recommandation, le Service de Notification et une API de Paiement tierce. L'équipe a entendu que « le Service de Recommandation est un peu instable » et souhaite s'assurer que, lorsqu'il échoue, le reste du système reste sain.
Aujourd'hui, le service utilise un seul pool de threads partagé de 200 threads & un seul pool de connexions HTTP partagé. Les quatre dépendances en aval sont en concurrence pour ces ressources. Il n'y a pas de disjoncteurs.
Concevoir une revue des modes de défaillance
Synthèse
Vous avez appris à identifier les points de défaillance unique (SPOF) par inspection, à reconnaître les schémas de défaillance en cascade, à distinguer le déclencheur du défaut latent lors de la lecture d'une analyse post-mortem, à rédiger des actions correctives sans blâme couvrant la prévention / la détection / la récupération, et à limiter le rayon d'impact à l'aide de cloisons (bulkheads) + disjoncteurs (circuit breakers) + dégradation gracieuse.
Appliquez les cinq.
Votre équipe lance un nouveau service search.example.com qui dépend de trois services en aval : un index de recherche principal (index.example.com), un service d'analyse (analytics.example.com) et un service de recommandations (recs.example.com). L'équipe souhaite que vous dirigiez une « revue des modes de défaillance » avant le lancement.
Où va ce cours ensuite
Où va ce cours ensuite
Vous savez maintenant identifier un SPOF, reconnaître une cascade, lire un post-morter de manière productive, rédiger des éléments d'action sans blâme et limiter la portée des incidents par conception.
La dernière leçon de ce cours (cs_distsys_observability_and_capacity) enseigne quoi mesurer pour détecter un problème avant que les utilisateurs ne le fassent. Vérifications de santé, points d'entrée de version, les quatre signaux dorés à un niveau de proxy et comment les décisions de capacité de pointe sont liées aux données observées.
Leçon complémentaire : geometry_of_failure_modes_and_blast_radius dérive la centralité d'intermédiarité (quel nœud du graphe est le goulot d'étranglement) et la coupe minimale (la limite de la portée des incidents).
Bravo. En avant.