Guide de fiabilité
Résilience des dépendances : délais d'attente, tentatives, contre-pression et solutions de repli
Un appel à une dépendance fait partie du domaine de défaillance de votre service. Limitez le temps, le travail et les tentatives qu'il peut consommer avant qu'un incident amont ne devienne votre panne.
La plupart des défaillances de production ne sont pas le crash d'un unique processus. Une base de données ralentit, la résolution DNS se bloque, une API tierce renvoie des erreurs ou une région perd de la capacité. Si les appelants attendent indéfiniment, réessaient sans limites et montent en charge vers la même dépendance contrainte, une défaillance partielle devient une panne en cascade.
La résilience des dépendances est l'ensemble des décisions explicites qui empêchent cette cascade. Elle comprend les délais d'attente, l'annulation, la politique de tentative, les limites de concurrence, les circuit breakers, les files, les solutions de repli et la télémétrie. Ces contrôles relèvent du comportement applicatif, pas d'un paramètre sidecar ou Kubernetes activable aveuglément.
Commencer par une carte des dépendances et un budget de latence
Pour chaque chemin de requête, cartographiez chaque dépendance synchrone et asynchrone : DNS, périphérie ou ingress, appels de service, base de données, cache, file, fournisseur d'identité, système de paiement et API externe. Enregistrez la responsabilité, le protocole, la latence normale et de queue, le comportement d'erreur, le quota, la limite de connexions, la criticité des données et l'option de repli.
Définissez ensuite une échéance de bout en bout depuis le point d'entrée exposé aux utilisateurs. Répartissez-la entre le travail nécessaire. Un budget utilisateur de 1 000 ms ne peut pas contenir trois délais d'attente client par défaut de 500 ms en série. Réservez du temps pour la mise en file, la sérialisation et une réponse ; n'allouez pas tout le budget aux appels aval.
Propagez les échéances et l'annulation à travers les appels de service. Lorsque le client est parti ou que l'échéance amont expire, poursuivre un travail coûteux consomme une capacité qui ne peut pas améliorer la réponse. Utilisez des horloges monotones pour les calculs de temps écoulé et distinguez, lorsque la bibliothèque cliente le permet, les délais d'attente de connexion, de handshake TLS, de premier octet, d'inactivité et de requête globale.
Ne réessayer que lorsque c'est sûr et abordable
Les tentatives peuvent améliorer la récupération après une défaillance transitoire, mais chaque tentative ajoute de la charge à une dépendance déjà potentiellement surchargée. Ne réessayez que les opérations idempotentes ou protégées par une clé d'idempotence. Ne réessayez jamais un paiement, une publication de message ou une mutation simplement parce que le client n'a pas reçu de réponse ; déterminez d'abord si le serveur a pu l'achever.
Utilisez un petit nombre de tentatives, un backoff exponentiel, du jitter et un temps écoulé maximal inférieur à l'échéance restante de l'appelant. Respectez Retry-After lorsque cela s'applique. Établissez un budget de tentatives, par exemple un faible pourcentage du trafic réussi, afin qu'un amont défaillant ne puisse pas multiplier indéfiniment votre volume de requêtes.
Évitez de réessayer à plusieurs couches. Si la périphérie, la gateway, le service mesh, le SDK et l'application réessaient chacun trois fois, une requête initiale peut devenir des dizaines d'appels de dépendance. Attribuez la responsabilité des tentatives à une seule couche par opération et observez les tentatives séparément des requêtes utilisateur.
Un délai d'attente est un contrôle de capacité
Une attente non bornée monopolise un worker, une connexion, de la mémoire et souvent un emplacement de requête. Une échéance raisonnable protège le budget de latence et conserve de la capacité pour les requêtes qui peuvent encore réussir. Réglez-la à partir de la latence de queue mesurée et des exigences produit, pas d'une valeur par défaut copiée.
Limiter la concurrence et appliquer la contre-pression
Chaque client de dépendance a besoin d'un nombre maximal d'appels simultanés et d'une file bornée. Une fois la limite atteinte, choisissez une réponse délibérée : mettre le travail en file avec une échéance, renvoyer un résultat mis en cache, écarter le trafic de faible priorité ou échouer rapidement avec une erreur réessayable. Une file interne non bornée transforme un bref ralentissement amont en pression mémoire et en forte latence de queue.
Dimensionnez les pools de connexions pour la dépendance et la flotte, pas simplement pour un conteneur. À l'échelle, les pools par pod peuvent submerger une base de données ou une gateway NAT. Suivez les connexions actives et inactives, le temps d'attente d'acquisition, le travail rejeté et le nombre de réplicas. Un autoscaler horizontal qui observe un CPU ou une profondeur de file élevés peut ajouter de la pression s'il n'est pas coordonné avec la capacité de la dépendance.
Utilisez des files asynchrones pour le travail qui n'a pas besoin de se terminer dans la réponse utilisateur. Définissez l'âge maximal, la politique de tentative et de dead-letter, la concurrence des consommateurs et le traitement idempotent. Les files fournissent un tampon, pas une capacité infinie : un âge croissant est un signal d'impact utilisateur et exige une décision d'écrêtage de charge ou de récupération.
Utiliser les circuit breakers et les solutions de repli avec prudence
Un circuit breaker s'ouvre lorsque la défaillance ou la latence franchit un seuil défini, empêchant d'autres appels pendant que la dépendance récupère. Il doit avoir un intervalle ouvert borné et un test semi-ouvert limité. Délimitez les breakers par dépendance et par opération ; une API de recommandations optionnelle lente ne doit pas bloquer le paiement.
Le comportement de repli doit préserver l'exactitude. De bonnes solutions de repli consistent à servir un catalogue mis en cache connu comme sûr, accepter une requête dans une file durable ou masquer un widget non essentiel. De mauvaises solutions de repli consistent à servir des données d'autorisation obsolètes, supprimer silencieusement une action financière ou renvoyer un succès pour un travail jamais enregistré.
Pour le travail qui modifie l'état, utilisez des patterns qui rendent la récupération explicite : clés d'idempotence, enregistrements transactional outbox, sagas avec actions compensatoires et jobs de réconciliation. Un circuit breaker n'améliore la disponibilité que lorsque le produit peut fournir une réponse véridique pendant l'indisponibilité de la dépendance.
Concevoir pour les réalités cloud-native et multi-région
Kubernetes redémarre les conteneurs défaillants, mais ne sait pas si réessayer une API externe est sûr ni si une base de données a atteint sa limite de connexions. Configurez la readiness pour refléter si un réplica peut servir en sécurité, mais ne transformez pas une défaillance de dépendance partagée en échec de liveness qui redémarre tous les appelants.
Déployez des réplicas à travers les domaines de défaillance lorsque le service l'exige, et testez le comportement lorsqu'une zone, un résolveur DNS, un émetteur d'identifiants ou une dépendance régionale échoue. Le routage multi-région ajoute ses propres préoccupations : retard de réplication, propriété des écritures, résidence des données, qualité des contrôles de santé et invalidation du cache. Envoyer une requête vers une région saine n'est pas utile si cette région ne peut pas servir les données requises en sécurité.
En périphérie, définissez les délais de connexion et de réponse de l'origine, les règles de tentative et les critères de failover en fonction des sémantiques applicatives. Limitez les tentatives automatiques aux méthodes sûres ou aux requêtes explicitement idempotentes. Les réponses pouvant être mises en cache peuvent être servies plus près des utilisateurs pendant une dégradation de l'origine, tandis que les chemins personnalisés et de mutation nécessitent une réponse dégradée honnête ou une file contrôlée. Surveillez la latence par région et par route, pas seulement les moyennes globales.
Instrumenter toute la chaîne d'appel
Utilisez OpenTelemetry pour créer des spans pour les appels de dépendance et propager le contexte de trace à travers les frontières HTTP, gRPC et de messagerie. Enregistrez le nom de l'opération, le système cible, le statut, la durée, le nombre de tentatives, la raison du délai d'attente et des attributs de route sûrs. Ne placez pas de tokens d'accès, de valeurs de requête complètes ni de données personnelles dans les attributs des spans.
Combinez les traces avec des métriques : taux de requêtes des dépendances, disponibilité, durée p50/p95/p99, nombre de délais d'attente, nombre de tentatives, état du circuit, âge de file, temps d'attente du pool et saturation. Corrélez-les avec les déploiements, les changements de configuration et les événements de routage en périphérie. L'objectif est de déterminer si les utilisateurs attendent le code applicatif, une dépendance partagée ou le chemin réseau.
Exercer la défaillance avant qu'elle ne vous exerce
Exécutez des tests contrôlés dans un environnement sûr. Ajoutez de la latence, renvoyez des réponses 429 et 503, blackholez une route, épuisez un pool de connexions, retardez un consommateur de file et faites tourner un identifiant de dépendance. Confirmez que les échéances bornent la requête, que les tentatives n'augmentent pas fortement, que les solutions de repli sont véridiques, que les alertes se déclenchent sur l'impact utilisateur et que la récupération ne nécessite pas de réparation manuelle du travail dupliqué.
Documentez la décision pour chaque dépendance critique : responsable, SLO, délai d'attente, règle de tentative, limite de concurrence, solution de repli, risque de perte de données et chemin d'escalade. Réexaminez-la à chaque modification de la dépendance, du profil de trafic ou de l'architecture régionale.
Références faisant autorité
- The Twelve-Factor App: Backing services
- Google SRE Book: Handling overload
- Google SRE Book: Addressing cascading failures
- OpenTelemetry: Instrumentation
- Kubernetes: Managing service accounts
Rendez les dépendances d'origine moins visibles pour les utilisateurs
Optimi peut vous aider à évaluer les modèles de mise en cache, de routage et de protection de l'origine qui complètent les contrôles de résilience au niveau de l'application.
Discuter d'une livraison résiliente