Timeout Pattern
Comment protéger vos services des attentes indéfinies en fixant des limites de temps sur les appels aux dépendances externes.
Quel problème résout-il
Lorsqu’un service en appelle un autre ou une ressource externe, l’appel peut rester en attente indéfiniment si le destinataire est lent ou ne répond pas. Sans timeout configuré, le service appelant maintient la connexion ouverte, consomme un thread du pool et finit par épuiser toutes ses ressources.
Sans Timeout:
Service A ──► Service B (lent, ne répond pas)
⏳ En attente... 10s
⏳ En attente... 30s
⏳ En attente... 60s
⏳ En attente... 120s
Thread du Service A bloqué indéfiniment
D'autres requêtes arrivent → davantage de threads bloqués
Thread pool épuisé → le Service A cesse de répondre
C’est l’un des problèmes les plus courants et les plus dangereux dans les systèmes distribués. Un service lent peut être pire qu’un service tombé en panne, car un service en panne échoue rapidement, tandis qu’un service lent consomme des ressources pendant une durée prolongée.
Comment ça fonctionne
Le pattern Timeout fixe une limite de temps maximale pour chaque appel à une dépendance. Si la réponse n’arrive pas dans le temps configuré, l’appel est annulé et une erreur est renvoyée ou un fallback est exécuté.
Avec Timeout (5 secondes):
Service A ──► Service B (lent)
⏳ En attente... 1s
⏳ En attente... 3s
⏳ En attente... 5s
⏠ TIMEOUT → Erreur ou fallback
Thread libéré après 5 secondes
Le Service A peut traiter d'autres requêtes
Types de timeout
| Type | Description | Exemple |
|---|---|---|
| Connection timeout | Temps maximal pour établir la connexion TCP | 2-5 secondes |
| Read/Response timeout | Temps maximal pour recevoir la réponse complète | 5-30 secondes |
| Request timeout | Temps total de l’opération (connexion + lecture) | 10-60 secondes |
| Idle timeout | Temps maximal d’inactivité sur une connexion ouverte | 30-120 secondes |
Comment choisir la valeur du timeout
Il n’existe pas de valeur universelle. Le timeout doit se baser sur des données réelles :
Étape 1: Mesurer la latence normale du service
P50 (médiane): 100ms
P95: 500ms
P99: 2s
Étape 2: Fixer le timeout avec une marge
Timeout = P99 × 2 = 4 secondes
Cela permet à 99% des appels normaux
d'aboutir, tout en coupant ceux qui prennent
un temps anormalement long.
| Critère | Timeout suggéré | Justification |
|---|---|---|
| API interne rapide | 1-3 secondes | Latence attendue < 200ms |
| API interne avec BD | 5-10 secondes | Les requêtes complexes peuvent prendre du temps |
| API externe (tiers) | 10-30 secondes | Moins de contrôle sur la latence |
| Opération batch | 60-300 secondes | Traitement lourd attendu |
| Health check | 2-5 secondes | Doit répondre vite, sinon il y a un problème |
Timeout en chaîne
Dans une chaîne d’appels, les timeouts doivent être décroissants pour éviter qu’un service intermédiaire attende plus longtemps que le service qui l’a appelé :
Client (timeout: 30s)
└──► API Gateway (timeout: 25s)
└──► Service A (timeout: 10s)
└──► Service B (timeout: 5s)
Si le Service B prend 6s:
Service B: timeout à 5s → erreur
Service A: reçoit l'erreur, peut faire un fallback
API Gateway: reçoit la réponse de A (avec fallback)
Client: reçoit la réponse dans ses 30s
Si les timeouts ne sont pas décroissants, il peut arriver que le client ait déjà abandonné la requête au moment où le service répond finalement, gaspillant ainsi des ressources.
Stratégies face à un timeout
| Stratégie | Description | Quand l’utiliser |
|---|---|---|
| Erreur immédiate | Renvoyer un HTTP 504 Gateway Timeout | Lorsqu’il n’y a pas d’alternative |
| Fallback | Renvoyer des données mises en cache ou par défaut | Lorsqu’il existe des données alternatives |
| Retry | Réessayer l’opération | Lorsque l’échec peut être transitoire |
| Dégradation | Offrir une fonctionnalité réduite | Lorsqu’une partie de la donnée suffit |
Avantages
- Libération des ressources : Les threads ne restent pas bloqués indéfiniment
- Fail fast : L’utilisateur reçoit une réponse (même une erreur) dans un délai prévisible
- Prévention des cascades : Un service lent n’entraîne pas ceux qui dépendent de lui
- Implémentation simple : La plupart des HTTP clients et frameworks prennent en charge les timeouts nativement
- Prévisibilité : Le temps de réponse maximal du système est connu et borné
- Base pour d’autres patterns : Le timeout alimente les métriques du Circuit Breaker et les tentatives du Retry
Trade-offs / Inconvénients
- Faux positifs : Un timeout trop agressif peut couper des opérations légitimes qui prennent plus de temps que la normale
- Opérations orphelines : Le service downstream peut terminer l’opération après le timeout, générant des incohérences
- Configuration délicate : Des valeurs incorrectes causent plus de problèmes qu’elles n’en résolvent
- Ne résout pas la cause racine : Le timeout protège l’appelant, mais le service lent doit tout de même être corrigé
- Complexité dans les chaînes : Coordonner les timeouts dans des chaînes de services requiert une conception soignée
Quand l’utiliser
- Tout appel à un service externe ou à une dépendance réseau (c’est une pratique obligatoire)
- Les requêtes vers des bases de données susceptibles de se bloquer à cause de locks ou de requêtes lentes
- Les appels à des API tierces à latence variable
- Toute opération d’I/O pouvant se bloquer indéfiniment
- En complément du Circuit Breaker (le timeout définit quand un appel est considéré comme échoué)
Quand l’éviter
- Les opérations locales en mémoire où le timeout n’a pas de sens
- Les processus batch de longue durée où le timeout doit être très élevé ou géré autrement (progress tracking)
- Les flux de données continus où la connexion doit rester ouverte (utiliser plutôt des heartbeats)
Technologies et implémentations courantes
| Catégorie | Options |
|---|---|
| HTTP Clients | Axios (Node.js), HttpClient (.NET), OkHttp (Java), requests (Python) |
| Frameworks | Spring Boot (configuration des timeouts), ASP.NET Core, Express.js |
| Bases de données | Connection pool timeouts, query timeouts, statement timeouts |
| Service Mesh | Istio, Linkerd, Envoy (timeouts au niveau de l’infrastructure) |
| Cloud | AWS ALB/NLB timeouts, Azure Application Gateway, Cloud Load Balancing |
Relation avec d’autres patterns
Requête
│
▼
┌──────────┐
│ Timeout │ ← Définit combien de temps attendre
└────┬─────┘
▼
┌──────────┐
│ Retry │ ← Réessaie en cas de timeout
└────┬─────┘
▼
┌──────────┐
│ Circuit │ ← S'ouvre s'il y a beaucoup de timeouts
│ Breaker │
└──────────┘
- Retry : Lorsqu’un timeout survient, le Retry peut réessayer l’opération
- Circuit Breaker : Les timeouts comptent comme des échecs ; de nombreux timeouts ouvrent le circuit
- Bulkhead : Isole les ressources afin que les timeouts d’un service n’affectent pas les autres
- Fallback : Stratégie de réponse lorsqu’un timeout survient
Prochaines étapes
Le Timeout est la base de toute stratégie de résilience. Pour gérer les tentatives après un timeout, explorez le pattern Retry. Pour comprendre comment les timeouts alimentent le Circuit Breaker, consultez l’article sur le Circuit Breaker.