1. Pourquoi penser à la « résilience » dans une ChatGPT App
Dans une application web classique, l’utilisateur voit au moins l’URL, le spinner du navigateur, peut rafraîchir la page. Dans ChatGPT, l’utilisateur n’a qu’un seul écran: le chat et votre App. Si quelque chose rame, il ne distingue pas le responsable — OpenAI, votre Gateway, le paiement ou le microservice d’analytics du voisin. Pour lui, c’est « ChatGPT + votre App ».
Quand un tool-call reste bloqué 30–60 secondes, le modèle attend, attend… et, au mieux, s’excuse pour le retard. Au pire, il hallucine une réponse au lieu de renvoyer des données depuis votre backend. La résilience n’est donc pas seulement une affaire de SRE et d’uptime, c’est aussi une question de qualité de réponse, de ton du modèle et de métriques dans le Store.
Dans l’écosystème ChatGPT App, nous avons plusieurs circuits indépendants:
- ChatGPT ↔ MCP Gateway.
- Gateway ↔ vos services backend/REST (Gift REST API, Commerce REST API, Analytics Service, etc.).
- Vos services ↔ des API externes (LLM, paiements, catalogues).
- Webhooks entrants (ACP, Stripe, intégrations diverses) ↔ vos handlers.
Le problème, c’est qu’une panne en un point peut déclencher un effet domino: le Gateway attend sagement un service bloqué, les workers se saturent, les connexions s’épuisent, les clients commencent à retenter, et en quelques minutes vous avez le scénario classique « tout brûle et coule en même temps ». C’est précisément contre cela que nous protègent les quatre patterns dont nous parlons aujourd’hui:
- Timeouts — on n’attend jamais indéfiniment.
- Circuit breaker — on n’essaie pas d’ouvrir une porte fermée à clé.
- Bulkheads — on construit des « compartiments » pour éviter de couler tout le navire.
- Protection contre les tempêtes de webhooks — on admet que les webhooks arrivent avec des doublons, des pics et des retries, et on s’y prépare.
2. Timeouts: on n’attend jamais indéfiniment
Qu’est-ce qu’un timeout et pourquoi c’est critique
Un timeout est le temps maximal pendant lequel votre code est prêt à attendre la réponse d’une dépendance: base de données, serveur MCP, API HTTP externe, modèle. Si la réponse n’arrive pas à temps — on considère l’appel comme un échec, on libère les ressources et on renvoie une erreur claire ou un fallback.
Sans timeout, les requêtes peuvent:
- rester en attente indéfiniment,
- occuper des connexions et le pool de threads,
- bloquer les requêtes suivantes,
- provoquer des pannes en cascade.
Le pattern est simple: « mieux vaut un échec prévisible après 3–5 secondes, qu’un silence incompréhensible pendant 5 minutes ».
Il faut se souvenir que nous avons des timeouts à plusieurs niveaux:
- au niveau du proxy/équilibreur (Cloudflare, Nginx),
- au niveau du MCP Gateway (clients HTTP vers les microservices),
- dans les services eux-mêmes (appels à la BD, API externes, LLM).
Pour ChatGPT, il est raisonnable de viser une durée totale de tool-call dans la plage 5–10 secondes pour les opérations courantes, et un maximum de 20–30 secondes pour les plus lourdes. Au-delà — c’est quasiment garanti d’offrir un mauvais UX.
Un fetchWithTimeout simple en TypeScript
Commençons par la pratique. Dans GiftGenius MCP Gateway, nous avons un client HTTP utilitaire qui parle au sélecteur de cadeaux, au service commerce, à l’analytics. Enrobons le fetch standard dans une fonction avec timeout:
// src/gateway/httpClient.ts
export async function fetchWithTimeout(
url: string,
opts: RequestInit & { timeoutMs?: number } = {}
) {
const { timeoutMs = 5000, ...rest } = opts;
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
try {
return await fetch(url, { ...rest, signal: controller.signal });
} finally {
clearTimeout(timeoutId);
}
}
Désormais, dans le code du Gateway, nous n’utilisons jamais un fetch « nu », seulement via ce helper:
// src/gateway/giftClient.ts
import { fetchWithTimeout } from "./httpClient";
export async function callGiftService(path: string) {
const res = await fetchWithTimeout(
process.env.GIFT_SERVICE_URL + path,
{ timeoutMs: 4000 }
);
if (!res.ok) {
throw new Error(`gift_service_${res.status}`);
}
return res.json();
}
Cette approche garantit que même si le service « gift » se bloque, au bout de 4 secondes nous coupons la connexion et pouvons renvoyer une erreur MCP à ChatGPT, au lieu de garder la connexion ouverte jusqu’au bout.
Où placer exactement les timeouts dans GiftGenius
Dans notre exemple GiftGenius:
- Au niveau du Gateway: timeouts sur les appels au Gift REST API, Commerce REST API, Analytics Service / REST API.
- À l’intérieur de ces services: timeouts sur les appels à la BD, ACP/paiements, API de recommandation externes.
- À l’entrée du Gateway: un timeout global de requête depuis ChatGPT, pour que le tool-call ne se transforme pas en « spinner infini ».
Il est important que le temps d’attente au niveau supérieur soit légèrement supérieur à celui des niveaux internes. Par exemple, si le Gateway attend le backend 5 secondes, et que le backend attend la BD 3 secondes, on a une marge pour le traitement et la sérialisation du résultat.
Comment expliquer les timeouts au modèle ChatGPT
Pour ChatGPT, il est important de renvoyer des erreurs sémantiques, plutôt que de faire tomber les connexions en silence. Au lieu d’un 500 abstrait, mieux vaut renvoyer une erreur MCP structurée que le modèle pourra formuler à l’utilisateur: « Le service de sélection de cadeaux est actuellement surchargé, réessayez un peu plus tard », etc.
Cela signifie que, dans le Gateway, en cas de timeout, il faut:
- Intercepter AbortError ou notre timeout_….
- Former une réponse MCP avec un code explicite et une brève description.
- Laisser au modèle la possibilité de décider comment l’expliquer à l’utilisateur.
Les timeouts résolvent le problème des requêtes bloquées, mais si une dépendance commence à tomber massivement, ils ne nous protègent pas d’une avalanche de tentatives identiques. Il nous faut alors un autre niveau de protection — le circuit breaker.
3. Circuit breaker: « disjoncteur » contre les services mourants
Intuition: pourquoi un simple timeout ne suffit pas
Nous avons appris à limiter le temps d’attente de chaque appel via des timeouts. Le timeout protège un appel donné. Mais si une dépendance est « vraiment morte » (par exemple, le service commerce tombe en OOM (Out Of Memory) à chaque requête), nous continuerons à l’appeler, à attendre 3–5 secondes à chaque fois, à capter une erreur, à charger le réseau et le CPU, et à recommencer.
Le circuit breaker ajoute de la mémoire: il suit les erreurs et les timeouts et, quand ils deviennent trop nombreux, il cesse d’envoyer des requêtes à ce service. À la place, il renvoie un échec rapide ou un fallback. Après un certain temps, il réessaie prudemment en mode half-open.
États classiques du disjoncteur:
- Closed — tout va bien, les requêtes passent.
- Open — le service est considéré « mort », les requêtes ne partent pas, erreur immédiate.
- Half-open — on essaie un nombre limité de requêtes; si elles réussissent — retour à closed, si ça retombe — de nouveau open.
Schéma simple de circuit breaker
Petit diagramme:
stateDiagram-v2
[*] --> Closed
Closed --> Open: trop d’erreurs
Open --> HalfOpen: cooldown expiré
HalfOpen --> Closed: plusieurs succès d’affilée
HalfOpen --> Open: erreurs à nouveau
Open --> Open: refus rapide
Mini-implémentation de circuit breaker en TypeScript
En production, on utilise généralement des bibliothèques prêtes à l’emploi (pour Node.js, par exemple, opossum ou des solutions maison légères), mais pour comprendre la mécanique, une classe compacte suffit.
Exemple d’un breaker très simplifié autour d’un appel au module commerce:
// src/gateway/circuitBreaker.ts
type State = "closed" | "open" | "half-open";
export class CircuitBreaker {
private state: State = "closed";
private failureCount = 0;
private nextAttemptAt = 0;
constructor(
private readonly failureThreshold = 5,
private readonly cooldownMs = 30_000
) {}
async call<T>(fn: () => Promise<T>): Promise<T> {
const now = Date.now();
if (this.state === "open") {
if (now < this.nextAttemptAt) {
throw new Error("circuit_open");
}
this.state = "half-open";
}
try {
const result = await fn();
this.onSuccess();
return result;
} catch (err) {
this.onFailure();
throw err;
}
}
private onSuccess() {
this.failureCount = 0;
this.state = "closed";
}
private onFailure() {
this.failureCount++;
if (this.failureCount >= this.failureThreshold) {
this.state = "open";
this.nextAttemptAt = Date.now() + this.cooldownMs;
}
}
}
Et utilisation dans le client du service commerce:
// src/gateway/commerceClient.ts
const commerceBreaker = new CircuitBreaker(3, 20_000);
export async function callCommerce(path: string) {
return commerceBreaker.call(async () => {
const res = await fetchWithTimeout(
process.env.COMMERCE_URL + path,
{ timeoutMs: 3000 }
);
if (!res.ok) throw new Error(`commerce_${res.status}`);
return res.json();
});
}
Ici, lorsque commerce commence à répondre massivement par des erreurs ou n’arrive pas à répondre avant le timeout, après plusieurs échecs le breaker passe à l’état open. Dans cet état, pendant cooldownMs, nous n’essayons plus du tout d’appeler le service et renvoyons immédiatement l’erreur circuit_open.
Ce que ChatGPT devrait voir quand le breaker « coupe » un service
Du point de vue de ChatGPT, il est préférable que vous:
- Répondiez rapidement par une erreur MCP « commerce_unavailable » ou « gift_service_overloaded ».
- Ajoutiez une description claire: « Le service de paiement est temporairement indisponible, essayons plus tard ».
- N’enfouissiez pas l’erreur sous des retries infinis.
C’est justement un cas où « un échec rapide et honnête » vaut mieux qu’un long blocage. Surtout au checkout: l’utilisateur tolèrera plus facilement un message clair que de regarder un spinner 40 secondes pour finir sur « quelque chose s’est mal passé ».
Les timeouts et le breaker nous protègent des dépendances « mauvaises » ou à l’arrêt, mais ils ne résolvent pas le problème où un type de charge consomme toutes les ressources et étouffe le reste du système. Pour cela, il faut un autre niveau — les bulkheads.
4. Bulkheads: isoler des « compartiments » pour qu’un seul ne coule pas tout le navire
Analogie avec un navire
Le pattern bulkhead tire son nom des cloisons étanches d’un navire: si une cale est percée, l’eau ne se répand pas dans tout le navire. En architecture, cela signifie: séparer les ressources entre différents axes de travail, afin qu’un service surchargé ne consomme pas tout — CPU, connexions, pools — et ne fasse pas tomber les chemins critiques.
Dans les microservices, on le fait généralement via des:
- pools de connexions HTTP distincts,
- pools de threads/workers distincts,
- files/sujets (topics) distincts,
- voire des clusters BD distincts pour les opérations critiques.
L’idée: si le service de recommandations de cadeaux ralentit et coince, il n’épuisera que ses propres ressources, sans casser le checkout et l’authentification.
Bulkheads dans le monde Node.js et MCP Gateway
En Node.js, nous n’avons pas des threads au sens classique (nous avons l’event loop et des workers), mais nous pouvons limiter le nombre de tâches parallèles pour chaque axe.
Exemple: dans le Gateway, il y a trois dépendances externes:
- Service Gift (sélection de cadeaux, appels LLM lourds).
- Service Commerce (checkout, ACP).
- Service Analytics (journalisation d’événements).
Nous pouvons introduire de simples limites sur les requêtes simultanées pour chacun d’eux.
Par exemple, un petit « sémaphore » pour limiter la parallélisation:
// src/gateway/bulkhead.ts
export class Bulkhead {
private active = 0;
private queue: (() => void)[] = [];
constructor(private readonly maxConcurrent: number) {}
async run<T>(fn: () => Promise<T>): Promise<T> {
if (this.active >= this.maxConcurrent) {
await new Promise<void>((resolve) => this.queue.push(resolve));
}
this.active++;
try {
return await fn();
} finally {
this.active--;
const next = this.queue.shift();
if (next) next();
}
}
}
Et utilisation pour les services:
// src/gateway/clients.ts
import { Bulkhead } from "./bulkhead";
const giftBulkhead = new Bulkhead(10); // jusqu’à 10 en parallèle
const commerceBulkhead = new Bulkhead(3); // checkout fortement limité
const analyticsBulkhead = new Bulkhead(50); // volume élevé possible
export async function callGiftWithBulkhead(fn: () => Promise<any>) {
return giftBulkhead.run(fn);
}
export async function callCommerceWithBulkhead(fn: () => Promise<any>) {
return commerceBulkhead.run(fn);
}
Ainsi, même si GPT décide de demander massivement « fais-moi 30 sélections de cadeaux complexes », elles seront exécutées à concurrence de 10 maximum en parallèle, et le checkout pourra continuer à fonctionner en utilisant sa propre limite.
GiftGenius: quels compartiments voulons-nous
Dans GiftGenius, il est logique de créer des compartiments séparés pour:
- La sélection de cadeaux (LLM lourds, moins critique, peut être ralentie).
- Checkout/ACP (très critique, à protéger au maximum).
- Analytics/logs (important, mais peut tolérer un peu de latence).
Dans une architecture plus avancée, vous déployez également ces parties comme des clusters séparés avec des ressources dédiées, mais dans le cadre de cette leçon, l’idée importante est: ne laissez pas des fonctionnalités secondaires « consommer tout l’oxygène ».
Ces trois patterns — timeouts, circuit breaker et bulkheads — concernent la manière dont vous appelez vers l’extérieur, vos dépendances. Mais il existe une autre classe de menaces pour la résilience: les flux d’événements entrants, qui peuvent vous submerger même avec des appels sortants parfaitement configurés. L’exemple le plus typique — les tempêtes de webhooks.
5. Tempêtes de webhooks: quand le monde vous envoie des événements plus vite que vous ne pouvez les traiter
Comment se comportent les webhooks en pratique
La quatrième source de problèmes de résilience — les événements entrants: webhooks depuis ACP, Stripe et autres systèmes. Ce sont eux qui peuvent provoquer une véritable « tempête », même si vous avez déjà des timeouts, des circuit breakers et des bulkheads.
Les webhooks ne sont pas des requêtes HTTP « à la demande », mais des événements « push » provenant de systèmes externes (Stripe, ACP, boutiques externes, etc.). Ils ont plusieurs propriétés pénibles:
- Livraison au moins une fois (at-least-once) — donc les doublons sont inévitables.
- L’ordre de livraison n’est pas garanti.
- En cas d’erreurs, ils aiment retenter: d’abord après une seconde, puis après 10, puis après une minute… jusqu’à ce que vous répondiez 2xx.
- En pic (par exemple pendant une vente), ils arrivent par paquets, créant une « tempête ».
Si votre handler n’est pas idempotent et prend trop de temps, il devient un goulot d’étranglement, toute la file se sature, et les retries ne font qu’amplifier la tempête. Au final, vous pouvez faire tomber la base, la file, les pools de workers — et, en cascade, le reste du système.
Principes de base pour se protéger des tempêtes
Voici quelques idées qui augmentent fortement vos chances de survie en cas de tempête:
Premièrement, queue-first, process-later. Idéalement, un webhook entrant ne doit pas exécuter un travail lourd de manière synchrone. À la place, il valide au plus vite la signature/le format, met une tâche dans une file et répond 200 OK. Le traitement se fait de manière asynchrone dans un worker. Si vous avez besoin d’une « confirmation rapide » pour ChatGPT, vous pouvez conserver un circuit de notifications séparé.
Deuxièmement, idempotence du handler. Un webhook répété pour la même opération ne doit pas « créer une commande une seconde fois » ni « débiter deux fois ». En général, on résout cela en stockant une clé d’idempotence ou un eventId et en vérifiant si nous avons déjà traité cet événement.
Troisièmement, rate limiting et circuit breaker côté récepteur. Même si l’émetteur tempête, vous pouvez:
- limiter le RPS par IP/abonnement/endpoint,
- renvoyer temporairement 429 ou 503 pour ralentir les retries,
- utiliser un breaker pour ne pas déverser le flux dans un downstream cassé (par ex., la BD des commandes).
Exemple de gestionnaire de webhook Next.js dans GiftGenius
Imaginons que nous ayons une ACP/plateforme de paiement qui envoie un webhook sur le statut de commande en POST /api/commerce/webhook. Nous voulons:
- accepter rapidement l’événement et le placer dans une file,
- ne pas le traiter de manière synchrone,
- ne pas tomber à cause des doublons.
Exemple simplifié (sans vérification de signature ni file réelle — ce sera dans les modules sur la sécurité et les files):
// app/api/commerce/webhook/route.ts
import { NextRequest, NextResponse } from "next/server";
// Ici on pourrait avoir Redis/queue ; pour l’instant on simule avec un tableau
const inMemoryQueue: any[] = [];
const processedEvents = new Set<string>(); // idempotence (pour la démo)
export async function POST(req: NextRequest) {
const event = await req.json();
const eventId = event.id as string;
if (processedEvents.has(eventId)) {
return NextResponse.json({ ok: true, duplicate: true });
}
// En réalité, on vérifierait ici la signature et le schéma
inMemoryQueue.push(event); // on met dans une file pour un traitement en arrière-plan
// Un worker en arrière-plan traitera plus tard et marquera l’ID comme traité
return NextResponse.json({ ok: true });
}
Pour l’instant, c’est une pseudo-implémentation, mais deux points sont essentiels:
- La partie synchrone est aussi légère que possible.
- Nous posons l’idempotence autour de event.id.
Dans la vraie vie, vous allez:
- utiliser une file externe (SQS, RabbitMQ, Kafka),
- stocker les événements traités dans la BD,
- vérifier la signature du webhook et la version du payload,
- éventuellement appliquer un Bulkhead/Breaker séparé autour du handler.
À quoi cela ressemble dans le contexte de GiftGenius
Pour GiftGenius, intégré à ACP/Stripe via des webhooks, la protection contre les tempêtes est particulièrement importante lors des saisons de pointe (Nouvel An, Black Friday). Il y a beaucoup d’événements:
- création d’intents,
- confirmation des paiements,
- annulations,
- remboursements.
Si votre handler commence à « s’allonger » (par exemple, à cause d’appels à une API externe), vous risquez que:
- l’ACP commence à retenter,
- les événements arrivent par paquets,
- la BD des commandes et le pool de workers se retrouvent saturés.
Le pattern « queue first » + idempotence + rate limiting à l’entrée sert précisément de filet de sécurité contre ces scénarios.
6. Comment ces patterns fonctionnent ensemble
Rassemblons maintenant tous ces patterns dans un seul scénario et voyons comment ils fonctionnent dans un flux réel « Proposer un cadeau et passer commande immédiatement ».
Considérons la chaîne « ChatGPT → Gateway → Service Gift → Commerce → webhooks » sur l’exemple suivant:
L’utilisateur dans le chat dit: « Propose un cadeau et passe directement la commande ».
- Le modèle décide d’appeler votre outil suggest_and_checkout.
- Le Gateway appelle le service gift via fetchWithTimeout et le bulkhead du service gift.
- Si le service gift se bloque — le timeout se déclenche; le breaker autour de lui passera à l’état open après un certain nombre d’échecs, et les requêtes suivantes recevront immédiatement une erreur MCP « gift_service_unavailable ».
- Si le service gift répond, le Gateway appelle le service commerce (de nouveau avec un timeout et un bulkhead séparé).
- Tout problème avec commerce déclenche un circuit breaker distinct, configuré plus strictement que celui de gift (car le checkout est critique).
- Une commande réussie conduit à un webhook depuis l’ACP vers votre /api/commerce/webhook, qui place l’événement dans une file et répond rapidement; des workers en arrière-plan traitent le paiement, et les webhooks répétés avec le même eventId sont ignorés comme doublons.
Au final:
- Un service de sélection bloqué ne fait pas tomber le checkout.
- Un commerce en panne ne transforme pas tous les tool-calls en spinner d’une minute — ChatGPT reçoit rapidement une erreur explicite.
- Les tempêtes de webhooks ne cassent pas votre circuit HTTP principal.
- Vous contrôlez les lieux de dégradation: mieux vaut désactiver temporairement les recommandations personnalisées que faire tomber le paiement.
7. Petit check-list pratique pour votre App (sous forme narrative)
En résumé, dans une ChatGPT App typique avec MCP/Gateway, il est pertinent d’aborder successivement les questions suivantes.
D’abord, vérifiez la présence de timeouts sur tous les appels externes. Tout le code fetch, les requêtes à la BD et au LLM doivent utiliser un wrapper comme fetchWithTimeout avec des valeurs adéquates. Il est important qu’il n’existe aucun endroit où une requête puisse rester suspendue indéfiniment.
Ensuite, identifiez vos dépendances les plus fragiles. En général, il s’agit des paiements, d’ACP, de grosses API externes et parfois de votre propre BD des commandes. Autour d’elles, il est logique d’ajouter un circuit breaker pour se protéger d’une avalanche de répétitions vers un service manifestement mort. En même temps, décidez dès le départ du comportement attendu de ChatGPT lorsque le breaker est en état open.
Après cela, considérez vos ressources comme des « compartiments ». Est-ce que tout passe par un seul pool de connexions et un seul pool de workers, ou bien les opérations critiques (login, checkout) ont-elles leurs limites de parallélisme, indépendantes du service de recommandation et de l’analytics ? Si ce n’est pas le cas — ajoutez une implémentation simple de bulkheads, ne serait-ce que comme une limite fruste sur les tâches parallèles.
Enfin, auditez tous les webhooks entrants. Vérifiez s’ils contiennent une clé d’idempotence ou un eventId, si vous n’essayez pas de faire un travail lourd de manière synchrone dans le handler HTTP et si vous pouvez survivre à une vague de retries si votre downstream tombe temporairement. Si non — déplacez la logique vers une file et des workers en arrière-plan.
Cette séquence d’étapes apporte un gain de résilience très significatif, même sans infrastructure ultra-sophistiquée.
8. Erreurs courantes avec les timeouts, circuit breakers, bulkheads et tempêtes de webhooks
Erreur n° 1: absence de timeouts « quelque part en bas ».
Les développeurs mettent souvent un timeout uniquement au niveau du Gateway ou uniquement au front-end, en oubliant qu’à l’intérieur du backend, il y a encore la BD, des API externes et le LLM. Au final, la requête externe a un timeout de 5 secondes, mais à l’intérieur, un appel à la BD ou au paiement peut rester bloqué des minutes, saturant le pool de connexions et provoquant des pannes en cascade.
Erreur n° 2: des timeouts gigantesques « au cas où ».
Parfois on met un timeout de 60–120 secondes: « qu’il ait le temps de finir ». Dans le contexte ChatGPT, c’est presque toujours mauvais. L’utilisateur part, le modèle se met à halluciner, et vos ressources restent bloquées tout ce temps. Bien mieux: un échec honnête au bout de 5–10 secondes avec une description claire.
Erreur n° 3: circuit breaker sans UX réfléchi.
On ajoute parfois un breaker « pour la forme », mais à son déclenchement, l’utilisateur ou le modèle reçoit un 500 abscons, « ECONNREFUSED » ou « axios error ». Le GPT ne peut alors pas expliquer correctement ce qui se passe et se met à inventer. Il faut prévoir dès le départ des formulations d’erreurs compréhensibles pour les humains et pour le modèle.
Erreur n° 4: mélange des ressources sans approche bulkhead.
Scénario classique: un service de recommandation (ou d’analytics) commence à ramer, consomme tout le pool de connexions à la BD ou le thread-pool, et, à sa suite, le checkout et le login tombent. Parce que les ressources ne sont pas séparées. L’absence d’une approche bulkhead, même minimale, fait qu’une fonctionnalité secondaire peut faire tomber toute la prod.
Erreur n° 5: traiter les webhooks comme des requêtes ordinaires.
Les débutants écrivent souvent le handler de webhook comme un contrôleur standard: longue logique métier, appels à des API tierces, absence d’idempotence. Avec des retries et des doublons, cela conduit à des doubles traitements d’événements, à des états bizarres des commandes et à des chutes sous charge en cas de tempête.
Erreur n° 6: ignorer l’idempotence dans les scénarios commerce.
Particulièrement dangereux: quand un webhook de paiement peut créer à nouveau une commande ou modifier son état une seconde fois. Sans vérification de la clé d’idempotence et stockage du statut de traitement de l’événement, vous finirez tôt ou tard avec un double débit ou des doublons de commandes.
Erreur n° 7: essayer de tout réparer avec des setTimeout et des « délais magiques ».
On tente parfois de contourner des race conditions et des problèmes de tempête en « attendant 100 ms et ça ira ». En pratique, cela rend le comportement encore plus instable et ne protège en rien des vraies pannes. La bonne voie — des timeouts explicites, un circuit breaker, des files et l’idempotence, pas de la magie avec des délais.
Erreur n° 8: absence de priorisation des parcours critiques.
Lorsque le checkout et le login vivent avec les mêmes limites que l’analytics ou la logique de recommandation, n’importe quelle surcharge peut faire tomber aussi bien le critique que le secondaire. Dans un design résilient, le checkout et l’auth — « vaches sacrées »: ressources séparées, limites séparées, alertes séparées et SLO dédiés.
GO TO FULL VERSION