Leçon 6 — Déployer et exploiter sur Cloud Run

Autoscaling, latence et coûts

6 min de lecture

Autoscaling, latence et coûts

Comment Cloud Run monte en charge

Cloud Run démarre des instances (des copies de votre conteneur) selon le trafic, et les arrête quand il n'y en a plus. Chaque instance traite jusqu'à --concurrency requêtes en même temps.

flowchart LR
  T["Trafic entrant"] --> LB["Répartiteur Cloud Run"]
  LB --> I1["Instance 1 : jusqu'à 20 requêtes simultanées"]
  LB --> I2["Instance 2 (démarrée si besoin)"]
  LB -.->|"au-delà de max-instances"| Q["Requêtes en attente, puis 429"]
  Z["Aucun trafic"] -.->|"min-instances = 0"| ZZ["0 instance : rien à payer pour le calcul"]

Une API IA a un profil particulier : une requête dure plusieurs secondes, mais l'instance ne calcule presque rien pendant ce temps, elle attend la réponse de Gemini. Comme le code est asynchrone (async def, await), une seule instance peut attendre de nombreuses réponses en parallèle sans bloquer.

RéglageValeur du labRaisonnement
--concurrency20requêtes surtout en attente réseau : une instance en gère plusieurs ; le défaut de Cloud Run est 80
--min-instances0coût nul au repos ; au prix d'un démarrage à froid sur la première requête
--max-instances2plafond de coût et protection du quota du modèle
--timeout60 sau-dessus du délai de la route (20 s), en dessous de la patience d'un client
--cpu 1 --memory 512Milargement suffisant pour une API qui relaie des appels

Pourquoi une concurrence de 1 serait-elle un mauvais choix pour cette API ?

Le démarrage à froid

Un démarrage à froid (cold start) est le temps nécessaire pour lancer une instance : télécharger l'image, démarrer Python, importer les bibliothèques. Il s'ajoute à la première requête après une période sans trafic. Leviers :

  • une image légère (python:3.12-slim, peu de dépendances) ;
  • des imports rapides (ne rien charger d'inutile au démarrage) ;
  • --min-instances 1 si la première requête doit être rapide, mais vous payez alors une instance en permanence ;
  • --cpu-boost : plus de CPU pendant le démarrage.

Où va la latence ?

Les journaux de l'API séparent le temps passé chez le modèle (llm_ms) et le temps total de la requête (latency_ms). Sur les appels mesurés (voir plus bas), l'écart est de quelques millisecondes : presque toute la latence vient du modèle. Optimiser le code Python ne servirait à rien ; les vrais leviers sont ailleurs :

LevierEffet sur la latence
modèle plus léger (variante lite)souvent plus rapide et moins cher, à valider sur la qualité
prompt et sortie plus courtsmoins de jetons à lire et à générer
streaming (SSE)même durée totale, mais le premier mot arrive vite
région proche des utilisateurs et du modèlemoins d'aller-retours réseau

Les coûts : deux postes

  1. Cloud Run : facturé au temps CPU et mémoire des instances (par défaut, seulement pendant le traitement des requêtes) et au nombre de requêtes. Avec min-instances=0, rien au repos.
  2. Le modèle : facturé au nombre de jetons en entrée et en sortie. C'est en général le poste principal d'une API IA.

Les journaux donnent les jetons de chaque appel. Méthode de calcul, avec des prix d'exemple à remplacer par ceux de la page Tarifs de votre modèle :

couˆt par ticket=jetons entreˊe×Pin+jetons sortie×Pout106\text{coût par ticket} = \frac{\text{jetons entrée} \times P_{in} + \text{jetons sortie} \times P_{out}}{10^6}

Par exemple, avec les 161 jetons d'entrée et 57 de sortie mesurés ci-dessous, et des prix hypothétiques de 0,30 et2,50et 2,50 par million de jetons : 161×0,30/106+57×2,50/106≈0,0002161 \times 0{,}30 / 10^6 + 57 \times 2{,}50 / 10^6 \approx 0{,}0002 $ par ticket.

Quels réglages bornent la dépense maximale si un appelant autorisé envoie des requêtes en boucle ? (plusieurs réponses)

Exercice : dimensionner

Vous attendez au plus 60 tickets simultanés aux heures de pointe, chacun prend environ 5 s. Le quota du modèle a été jugé suffisant. Proposez --concurrency et --max-instances, et dites ce qui arrive au 61e ticket simultané.

Voir la solution

Par exemple --concurrency 20 --max-instances 3 : 3 × 20 = 60 requêtes simultanées. On peut prendre une marge (--max-instances 4) si le budget le permet.

Au-delà, Cloud Run met brièvement la requête en attente d'une place libre ; si aucune ne se libère à temps, il répond 429. Le client doit donc réessayer avec attente. Vérifiez aussi que le quota du modèle suit : plus d'instances ne sert à rien si Gemini renvoie lui-même des 429, ce qui est arrivé pendant ce lab (voir plus bas).

Résultat obtenu

Trois appels successifs à /v1/tickets/analyze sur le service déployé, le 2026-10-15 (temps total vu du client, depuis un poste en France) :

analyze 200 2.748863s
analyze 200 5.672453s
analyze 503 4.851666s

Et les journaux correspondants dans Cloud Logging :

INFO  {"message": "analyse", "llm_ms": 2673, "tokens_in": 161, "tokens_out": 57, "model": "gemini-2.5-flash", ...}
INFO  {"message": "requete", "path": "/v1/tickets/analyze", "status": 200, "latency_ms": 2675}
ERROR {"message": "llm_error", "code": 429, "detail": "429 RESOURCE_EXHAUSTED. ..."}
INFO  {"message": "requete", "path": "/v1/tickets/analyze", "status": 503, "latency_ms": 4779}

Le troisième appel a reçu un 429 de Vertex AI (capacité partagée momentanément saturée) : le SDK a réessayé, puis l'API a renvoyé un 503 avec Retry-After, comme prévu en leçon 1.2. Le même ticket a été classé haute puis urgente sur deux appels : même à temperature=0, la sortie peut varier.

Je sais faire

  • Choisir concurrence et instances pour une API qui attend un modèle
  • Expliquer le démarrage à froid et ses leviers
  • Lire llm_ms et latency_ms pour savoir où passe le temps
  • Estimer le coût par requête à partir des jetons journalisés
Suivant