Chaque délai d'expiration doit être plus court que celui d'avant
L'affirmation La plupart des pannes en cascade ne viennent pas d'un composant défaillant. Elles viennent d'un composant lent alors que chaque délai d'expiration de la pile est plus...
L'affirmation
La plupart des pannes en cascade ne viennent pas d'un composant défaillant. Elles viennent d'un composant lent alors que chaque délai d'expiration de la pile est plus long que celui de la couche en amont : rien n'abandonne dans le bon ordre, et chaque couche retient des ressources en attendant une couche déjà abandonnée. Les délais doivent décroître à mesure qu'on progresse vers l'intérieur. Sinon, votre système n'a aucun moyen de délester.
Ce qui déraille avec les valeurs par défaut
Prenez une pile courante fonctionnant entièrement sur les valeurs par défaut :
| Couche | Défaut typique | Effet |
|---|---|---|
| Navigateur | ~300 s | L'utilisateur a abandonné à 8 s |
| RDC | 30 s | Renvoie un 504 |
Nginx proxy_read_timeout | 60 s | Attend encore après l'abandon du RDC |
| Requête applicative | aucun | Attend indéfiniment |
| Requête de base de données | aucun | Attend indéfiniment |
Une requête lente occupe désormais un processus applicatif aussi longtemps que la base le décide. Le RDC a déjà annoncé l'échec au client à 30 secondes, et le client a déjà rechargé — créant une seconde requête qui attend elle aussi. Vos processus se remplissent de requêtes que plus personne n'écoute, et le site cesse de répondre à tout, y compris aux pages sans rapport avec la requête lente.
La règle d'ordonnancement
Le délai de chaque couche doit être plus court que celui de la couche qui la précède, avec assez de marge pour renvoyer une erreur utile :
RDC / repartiteur 30 s
Nginx proxy_read 25 s
Requete appli 20 s
Requete BD 8 s
Client HTTP 5 s (avec 1 reprise = 10 s au pire)
Les budgets de la base et des appels HTTP sortants doivent tenir dans le budget applicatif reprises comprises. Un délai client de 5 secondes avec trois reprises fait 15 secondes, ce qui fait éclater un budget de 20 secondes dès que quoi que ce soit d'autre est lent dans le gestionnaire. Comptez le pire cas, pas le chemin heureux.
Les configurer
# Postgres, par connexion ou par role
SET statement_timeout = '8s';
SET idle_in_transaction_session_timeout = '30s';
# Nginx
proxy_connect_timeout 3s;
proxy_send_timeout 25s;
proxy_read_timeout 25s;
idle_in_transaction_session_timeout est celui que presque tout le monde omet, et il prévient une défaillance distincte et plus vicieuse : une application qui ouvre une transaction, appelle une API externe lente à l'intérieur, et conserve des verrous en empêchant le nettoyage pendant tout ce temps. Réglez-le et cette catégorie d'incident disparaît.
Les délais de connexion doivent être courts : trois secondes sont généreuses pour une poignée de main TCP à l'intérieur d'une région. Un long délai de connexion n'apporte rien : si la connexion ne s'établit pas vite, l'hôte n'est pas là.
La taille du bassin est un problème de délai déguisé
Les délais ne délestent que s'il existe une limite de file derrière eux. Un bassin de 20 connexions avec 8 processus signifie que 8 requêtes peuvent être en vol et que le reste attend — combien de temps ? Fixez-le explicitement :
pool_acquire_timeout = 2s
Échouer rapidement avec un 503 quand le bassin est épuisé est le bon comportement. Cela déleste l'excédent, borne la file et permet aux requêtes servables d'aboutir vite. Mettre tout en file jusqu'au rétablissement de la base, c'est offrir au rétablissement un arriéré qu'il ne pourra pas résorber.
Les reprises aggravent le problème avant de l'améliorer
Une reprise est une requête supplémentaire envoyée à un système déjà en difficulté ; c'est pourquoi une logique de reprise naïve transforme un ralentissement bref en panne durable. Trois règles les gardent utiles. Ne reprenez que des opérations idempotentes, ou porteuses d'une clé d'idempotence. Ajoutez du bruit — quelques centaines de millisecondes réparties au hasard — pour que mille clients ne reprennent pas à l'unisson et ne reproduisent pas la pointe initiale exactement une seconde plus tard. Et cessez complètement de reprendre dès que le taux d'échec vers une dépendance franchit un seuil, en laissant les requêtes échouer vite jusqu'au rétablissement.
Testez délibérément
Provoquez la lenteur exprès hors production et observez :
psql -c "SELECT pg_sleep(30)" # dans une session
ab -n 200 -c 20 https://essai.exemple.ca/ # observer les codes de statut
Le résultat correct : des 503 rapides sur la route touchée et un service normal partout ailleurs. Si au contraire le site entier devient inerte, vos délais sont mal ordonnés, et vous venez de le découvrir en contexte contrôlé plutôt que pendant la panne d'un fournisseur.
Écrivez-les quelque part
Consignez l'échelle complète dans un fichier de votre dépôt, avec la valeur de chaque couche et une ligne d'explication. Les délais se configurent à cinq endroits différents par cinq personnes différentes sur trois ans et, sans source unique, l'ordonnancement s'inverse silencieusement la prochaine fois que quelqu'un augmente une valeur pour régler une plainte sans rapport au sujet d'un rapport lent.