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

TypeDescriptionExemple
Connection timeoutTemps maximal pour établir la connexion TCP2-5 secondes
Read/Response timeoutTemps maximal pour recevoir la réponse complète5-30 secondes
Request timeoutTemps total de l’opération (connexion + lecture)10-60 secondes
Idle timeoutTemps maximal d’inactivité sur une connexion ouverte30-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èreTimeout suggéréJustification
API interne rapide1-3 secondesLatence attendue < 200ms
API interne avec BD5-10 secondesLes requêtes complexes peuvent prendre du temps
API externe (tiers)10-30 secondesMoins de contrôle sur la latence
Opération batch60-300 secondesTraitement lourd attendu
Health check2-5 secondesDoit 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égieDescriptionQuand l’utiliser
Erreur immédiateRenvoyer un HTTP 504 Gateway TimeoutLorsqu’il n’y a pas d’alternative
FallbackRenvoyer des données mises en cache ou par défautLorsqu’il existe des données alternatives
RetryRéessayer l’opérationLorsque l’échec peut être transitoire
DégradationOffrir une fonctionnalité réduiteLorsqu’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égorieOptions
HTTP ClientsAxios (Node.js), HttpClient (.NET), OkHttp (Java), requests (Python)
FrameworksSpring Boot (configuration des timeouts), ASP.NET Core, Express.js
Bases de donnéesConnection pool timeouts, query timeouts, statement timeouts
Service MeshIstio, Linkerd, Envoy (timeouts au niveau de l’infrastructure)
CloudAWS 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.