Aller au contenu
Ingénierie · 5 min de lecture

Fixez un délai d'instruction pour qu'une requête ne coule pas le site

L'affirmation Une seule requête lente, laissée s'exécuter sans limite de temps, peut faire tomber une application entière — non parce que la requête elle-même est importante, mais...

A Rédigé par Administrator
Fixez un délai d'instruction pour qu'une requête ne coule pas le site

L'affirmation

Une seule requête lente, laissée s'exécuter sans limite de temps, peut faire tomber une application entière — non parce que la requête elle-même est importante, mais parce que, pendant qu'elle retient une connexion et des verrous, chaque autre requête s'empile derrière elle jusqu'à ce que le bassin de connexions soit épuisé et que tout le système cesse de répondre. Un délai d'instruction, réglé à la base, est une configuration d'une ligne qui convertit cette défaillance en cascade en une seule requête échouée, et son absence est l'une des raisons les plus courantes pour lesquelles un petit problème de performance devient une panne complète.

Comment une requête fait tout tomber

Le mécanisme est un embouteillage. Une requête qui devrait prendre 10 millisecondes en prend plutôt 90 secondes — un index manquant après un changement de taille de données, une jointure croisée accidentelle, un verrou qu'elle attend. Pendant qu'elle tourne, elle retient une connexion de votre bassin limité. D'autres requêtes arrivent, chacune ayant besoin d'une connexion ; elles attendent. La requête lente tourne toujours, donc les connexions qu'elle et d'autres retiennent ne sont pas rendues. En quelques secondes, le bassin est entièrement retiré, et désormais chaque requête — y compris les rapides et saines qui n'ont rien à voir avec la lente — ne peut obtenir de connexion et échoue. Une mauvaise requête est devenue une indisponibilité totale, et le site est en panne non parce qu'il est surchargé mais parce qu'il attend.

La défense d'une ligne

Un délai d'instruction dit à la base d'annuler toute requête qui tourne plus longtemps qu'une limite fixée, libérant sa connexion et ses verrous. Réglez-le globalement comme filet :

-- defaut a l'echelle de la base : aucune requete ne tourne plus de 15s
ALTER DATABASE app SET statement_timeout = '15s';

Ainsi, la requête de 90 secondes est annulée à 15 secondes. La requête qui l'a émise obtient une erreur — ce qui est mauvais, mais borné — et sa connexion retourne au bassin immédiatement. L'embouteillage ne se forme jamais, car aucune requête ne peut retenir une connexion assez longtemps pour en amorcer un. Une seule requête échoue ; le site reste debout. C'est tout le marché, et c'est presque toujours le bon, car une requête échouée est incomparablement meilleure que toutes les requêtes qui échouent.

Fixez des limites différentes selon le travail

Un délai global unique est un filet grossier, et le meilleur motif est d'ajuster la limite au genre de travail. Les requêtes Web interactives devraient avoir un délai serré, car un utilisateur attend et une requête qui prend même quelques secondes a déjà échoué de son point de vue. Les tâches de fond et les rapports prennent légitimement plus de temps et devraient pouvoir le faire, sur leurs propres connexions avec leurs propres limites plus généreuses :

-- limite serree pour le role web interactif
ALTER ROLE web_app SET statement_timeout = '5s';

-- limite genereuse pour le role de fond/rapports
ALTER ROLE batch_worker SET statement_timeout = '5min';

C'est le raffinement important : le délai qui protège votre trafic interactif devrait se mesurer en secondes à un chiffre, car tout ce qui est plus lent est déjà une expérience utilisateur ratée, tandis que la requête de rapport qui a vraiment besoin de deux minutes tourne comme un autre rôle sur une autre connexion et ne peut pas, par construction, épuiser le bassin qui sert vos utilisateurs. Séparer ces deux charges est ce qui vous laisse fixer un délai agressif là où cela compte sans casser le travail lent légitime.

Ajoutez aussi un délai sur l'acquisition

Le délai d'instruction borne la durée d'exécution d'une requête ; une limite distincte devrait borner la durée d'attente d'une requête pour obtenir une connexion au départ. Si le bassin est momentanément occupé, une requête devrait attendre brièvement puis échouer vite plutôt que de se figer indéfiniment, car une requête figée sur l'acquisition d'une connexion est tout aussi coincée qu'une figée sur une requête lente, et échoue l'utilisateur tout aussi complètement :

# configuration du bassin applicatif
acquire_timeout: 3s     # echouer vite si aucune connexion n'est libre

Ensemble, le délai d'instruction et le délai d'acquisition posent une borne supérieure sur la durée de quoi que ce soit : une requête obtient vite une connexion et exécute une requête garantie de finir ou d'être annulée dans la limite, ou elle échoue vite et proprement. Rien ne se fige, et parce que rien ne se fige, rien ne s'empile.

Rendez le délai visible quand il se déclenche

Une requête annulée est un signal, pas seulement une erreur à avaler. Quand une instruction expire, journalisez-la avec la requête et la durée, car un délai qui se déclenche signifie que quelque chose ne va vraiment pas — un index manquant, un problème de contention de verrous, une requête à optimiser — et le délai vous a acheté la chance de corriger calmement au lieu de pendant une panne. Traitez un taux croissant de délais comme une alerte précoce qu'une requête se dégrade, et enquêtez avant qu'elle ne se dégrade au point où même la protection du délai soit un flux constant d'erreurs vues par l'utilisateur.

Ce qu'il faut régler aujourd'hui

Ajoutez un statement_timeout global comme filet si vous n'en avez aucun — cela seul retire la pire version de la cascade. Puis séparez votre travail de base interactif et de fond en rôles différents et donnez au rôle interactif un délai mesuré en secondes. Ajoutez un délai d'acquisition dans votre bassin de connexions. Ces trois changements ensemble font qu'aucune requête lente unique, et aucune pression momentanée du bassin, ne peut se transformer en tout le site qui se fige — la défaillance reste contenue à la seule requête qui l'a causée, exactement là où une défaillance devrait rester.

#postgresql #reliability #performance #operations

À lire aussi