Automation
Azure API Management : contenir une tempête de retries avant de scaler le backend
Un runbook de production pour prouver l'amplification des retries dans Azure API Management, protéger un backend saturé et choisir une policy bornée, un circuit breaker ou un rollback.
Quand un backend API commence à répondre 429, 502, 503 ou 504, les retries peuvent transformer un ralentissement limité en panne. Le client retente, Azure API Management retente, puis le SDK du backend peut retenter à son tour. Scaler le backend absorbe parfois la charge pendant quelques minutes, mais ne supprime pas l’amplification. L’incident suivant devient seulement plus coûteux et plus difficile à expliquer.
Le cas d’usage est une API de commandes exposée par Azure API Management. Un backend de tarification ralentit pendant l’incident d’une dépendance. La policy APIM relance trois fois les appels en échec, tandis que les clients retentent aussi deux fois. L’objectif du runbook est de prouver où les retries se produisent, de réduire la pression sans perdre les preuves, puis de décider entre modification de la policy, circuit breaker du backend, capacité temporaire ou restauration de la policy précédente.
Figer un seul chemin de requête
Partez d’une opération et d’une fenêtre contrôlée. Un graphique agrégé de 5xx sur toutes les API ne suffit pas pour diagnostiquer une amplification.
incident: inc-2026-08-20-012
window_utc:
start: 2026-08-20T06:30:00Z
end: 2026-08-20T07:00:00Z
request:
api: orders-api
operation: GET /orders/{id}/price
correlation_id: retry-check-20260820-01
backend:
apim_backend_id: pricing-backend-prod
expected_timeout_seconds: 8
retry_layers:
client: 2
apim: 3
backend_sdk: unknown
decision_needed:
- reduce_or_remove_apim_retry
- trip_backend_circuit_breaker
- temporary_capacity_change
- rollback_last_policy Notez si l’opération est réellement en lecture seule selon son contrat ou si elle est protégée par une clé d’idempotence. Retenter une lecture de prix n’a pas le même risque que rejouer une création de commande ou un paiement.
Calculer l’amplification possible
Les retries se multiplient entre les couches. Si le client exécute un appel initial et deux retries, et qu’APIM exécute un appel backend initial puis trois retries pour chaque appel client, une action utilisateur peut produire jusqu’à douze appels backend avant même les retries du SDK.
Maximum de tentatives backend
tentatives client = 1 initiale + 2 retries = 3
tentatives APIM par appel client = 1 initiale + 3 retries = 4
appels backend possibles = 3 x 4 = 12
Il s'agit d'une borne haute, pas d'un volume observé.
Prouver les tentatives réelles avec des logs backend corrélés avant tout changement. Cette estimation aide à qualifier l’urgence, mais ce n’est pas une preuve. La condition de la policy peut arrêter les retries plus tôt, certains clients ne retentent pas et les erreurs de connexion peuvent suivre un autre chemin.
Lire les symptômes APIM avec KQL
ApiManagementGatewayLogs expose la réponse au client, la réponse du backend, la durée backend, la durée totale et la dernière erreur de policy. Une ligne décrit une requête reçue par le gateway ; elle ne prouve pas automatiquement combien d’appels backend ont été exécutés dans un bloc de retry.
let Start = datetime(2026-08-20T06:30:00Z);
let End = datetime(2026-08-20T07:00:00Z);
ApiManagementGatewayLogs
| where TimeGenerated between (Start .. End)
| where ApiId == "orders-api" and OperationId == "get-order-price"
| summarize gatewayRequests=count(),
failedGatewayRequests=countif(ResponseCode >= 500 or ResponseCode == 429),
backend429=countif(BackendResponseCode == 429),
backend5xx=countif(BackendResponseCode between (500 .. 599)),
p95BackendMs=percentile(BackendTime, 95),
p95TotalMs=percentile(TotalTime, 95),
errors=make_set(LastErrorReason, 10)
by bin(TimeGenerated, 5m), BackendId
| order by TimeGenerated asc Comparez cette chronologie avec les logs de requêtes du backend, les métriques de dépendance et la télémétrie de retry des clients. Une hausse de BackendTime suivie de 429 ou 503 soutient l’hypothèse de saturation. Un backend stable avec uniquement des erreurs de policy au gateway oriente ailleurs.
Prouver chaque tentative à la frontière du backend
Pour un test contrôlé, ajoutez temporairement un marqueur de tentative dans le bloc de retry APIM et conservez l’identifiant de corrélation entrant. Le backend doit journaliser ces deux headers. Limitez cette instrumentation à l’opération touchée ou à une révision de test, pas à toutes les API.
<backend>
<retry
condition="@(context.Response != null && (context.Response.StatusCode == 429 || context.Response.StatusCode == 502 || context.Response.StatusCode == 503 || context.Response.StatusCode == 504))"
count="2"
interval="2"
max-interval="8"
delta="2"
first-fast-retry="false">
<set-variable name="attempt" value="@((context.Variables.ContainsKey("attempt") ? (int)context.Variables["attempt"] : 0) + 1)" />
<set-header name="X-APIM-Attempt" exists-action="override">
<value>@(((int)context.Variables["attempt"]).ToString())</value>
</set-header>
<set-header name="X-Correlation-Id" exists-action="skip">
<value>@(context.RequestId.ToString())</value>
</set-header>
<forward-request buffer-request-body="true" />
</retry>
</backend> La policy retry exécute d’abord ses policies enfants, puis évalue s’il faut effectuer une nouvelle tentative. count="2" autorise donc deux retries après l’exécution initiale. Ne lisez pas count comme le nombre total d’appels backend.
Séparer erreurs retentables et retries dangereux
Une policy de production doit porter un contrat d’échec explicite.
Opération en lecture seule, 502/503/504 court
Autoriser peu de retries avec backoff et budget de latence global
429 avec Retry-After
Respecter le temps de récupération du backend
Préférer le circuit breaker aux appels immédiats répétés
POST ou opération avec effet sans preuve d'idempotence
Ne pas ajouter de retry automatique
Réconcilier l'état backend avant tout rejeu
Timeout avec état backend inconnu
Considérer le résultat comme ambigu
Interroger l'enregistrement métier avant de retenter
Erreur de policy avant l'appel backend
Corriger la policy ou le chemin d'identité
Scaler le backend ne changera rien La distinction importante n’est pas seulement le code HTTP. Il faut savoir si la tentative précédente a pu modifier l’état et si la suivante reste dans un budget documenté de latence et de capacité.
Contenir la pression sans masquer l’incident
La première action de production doit réduire l’amplification tout en conservant le diagnostic. Pour une lecture, réduisez le nombre de retries, désactivez le premier retry immédiat et ajoutez un backoff. Pour une opération risquée, retirez le retry et retournez un échec explicite pendant la réconciliation.
Un circuit breaker de backend est adapté lorsque les échecs répétés doivent interrompre les appels pendant une période de récupération. Dans APIM, un circuit ouvert retourne 503 jusqu’à sa réinitialisation. La décision reste approximative entre les instances distribuées du gateway : c’est une protection, pas un compteur global exact. Elle doit être accompagnée d’un comportement client défini, de logs et d’un chemin de reprise testé.
action:
operation: get-order-price
mode: bounded_retry
retry_count: 1
first_fast_retry: false
total_latency_budget_ms: 12000
backend_protection:
circuit_breaker: evaluate
failure_codes: [429, 500-599]
accept_retry_after: true
still_enabled:
- gateway diagnostics
- backend request logs
- synthetic read-only probe
blocked:
- retries on order creation
- global APIM policy change
- capacity increase without an expiry time Ne déployez pas une limite de débit, une policy de retry ou un circuit breaker global à partir d’un seul échantillon d’incident. Le périmètre doit rester limité au backend et aux opérations dont le contrat d’échec est compris.
Décider entre changement, scaling et rollback
Croisez trois signaux : amplification des retries, saturation du backend et impact métier.
Modifier la policy de retry quand
Les logs backend corrélés prouvent les tentatives répétées
L'opération accepte le retry
Le backoff et le budget de latence sont définis
Configurer ou ajuster le circuit breaker quand
Le backend a besoin d'une fenêtre de récupération
Les conditions d'ouverture et la durée sont testables
Les clients gèrent le 503 et le chemin Retry-After
Scaler temporairement quand
La demande reste légitime après réduction de l'amplification
La capacité a un propriétaire, une limite et une échéance
Rollbacker quand
La tempête commence après un déploiement de policy
Les probes de garde-fou échouent ou des effets sont dupliqués
La policy précédente est connue et déployable Après le changement, rejouez une probe positive et une probe d’échec. Vérifiez que l’appel sain réussit, que l’échec ne dépasse pas le nombre de tentatives autorisé, que la pression backend baisse, que la corrélation reste visible et que les opérations risquées ne sont pas rejouées. Si un garde-fou échoue, restaurez la policy précédente et gardez ouverte la décision de protection du backend.
Conclusion
Une tempête de retries est un incident d’amplification, pas seulement un incident de capacité. Les preuves utiles sont un chemin de requête, le nombre de tentatives à chaque couche, les logs gateway et backend corrélés, le contrat d’idempotence de l’opération et un budget de latence borné.
La décision devient alors défendable : réduire les retries quand APIM amplifie l’échec, utiliser un circuit breaker quand le backend a besoin de récupérer, scaler seulement après réduction de l’amplification, ou restaurer la dernière policy si elle a créé la tempête. L’objectif n’est pas que chaque appel finisse par réussir. Il est de garder l’échec contrôlé, observable et réversible.