L'argument de départ de ce projet était double : ne plus payer une VM allumée en permanence pour vingt minutes de travail par nuit, et ne plus payer deux fois les appels Gemini lors des relances. Il faut maintenant le prouver avec des chiffres mesurés, dire comment ils évoluent quand le volume est multiplié par mille, puis supprimer l'environnement de test. Un workflow planifié oublié continue de tourner chaque nuit, et d'appeler Gemini sur des entrées vides ou, pire, sur de vraies données copiées « pour essayer ».
Le mécanisme
Chaque service est facturé à l'usage, avec des unités différentes :
flowchart LR N["Une nuit<br/>29 avis"] --> G["Vertex AI<br/>tokens d'entrée et de sortie"] N --> R["Cloud Run jobs<br/>vCPU-s et Gio-s des tâches"] N --> W["Workflows<br/>pas exécutés"] N --> SC["Cloud Scheduler<br/>par planification et par mois"] N --> GCS["Cloud Storage<br/>Go stockés, opérations"] G --> F["Facture"] R --> F W --> F SC --> F GCS --> F
| Poste | Unité | Ce qui le fait grossir |
|---|---|---|
| Gemini 2.5 Flash (Vertex AI) | 0,30 en sortie (tarif public au 2026-10-16) | le nombre d'avis, la longueur des réponses, les relances qui recalculent |
| Cloud Run jobs | durée de chaque tâche × vCPU et mémoire alloués | le démarrage des conteneurs, plus que le calcul lui-même ici |
| Workflows | pas exécutés ; les premiers milliers de chaque mois sont gratuits | les boucles (for sur les tranches et les thèmes) |
| Cloud Scheduler | 0,10 $ par planification et par mois, 3 planifications gratuites par compte de facturation | le nombre de planifications, pas leur fréquence |
| Cloud Storage | quelques Ko par nuit | négligeable, sauf rétention de données brutes sur des années |
La mise en œuvre
Les tokens sont mesurés à la source et remontés jusqu'à la notification : tokens dans chaque stats.json (somme de usage_metadata.total_token_count des lots), tokens_synthese lu dans la réponse du connecteur Vertex AI (gemini.usageMetadata.totalTokenCount). La durée des tâches est dans les journaux (duree_ms) et dans gcloud run jobs executions list.
Le nettoyage supprime dans l'ordre qui évite qu'un composant se réveille pendant la suppression : d'abord la planification, puis le workflow et le job, puis ce qu'ils utilisent.
echo "==> Planification d'abord (plus aucun déclenchement), puis workflow et job"
$G scheduler jobs delete "$P-nuit" --location="$REGION" 2>/dev/null
$G workflows delete "$P" --location="$REGION" 2>/dev/null
$G run jobs delete "$P-job" --region="$REGION" 2>/dev/null
...
# Les listes IAM sont à cohérence différée : un compte supprimé peut y figurer encore quelques secondes.
for i in 1 2 3; do
"$(dirname "$0")/cloud-status.sh" --strict && exit 0
[ "$i" -lt 3 ] && { echo "Nouvelle vérification dans 15 s."; sleep 15; }
done
exit 1Résultat obtenu (2026-10-16)
Une nuit de 29 avis, mesurée sur la dernière exécution planifiée :
| Mesure | Valeur |
|---|---|
| Tokens du classement (6 tâches, 6 lots) | 4 826 |
| Tokens de la synthèse | 1 538 |
| Durée de l'exécution du workflow | 74 s (déclenchement planifié forcé) |
| Durée de chaque exécution de job (2 tâches) | 18 à 22 s, dont environ 2 s de travail par tâche |
| Reprise après perte d'une tranche | 73 s, 716 tokens de classement au lieu de 4 826 |
Coût Gemini de cette nuit : entre 0,2 et 1,6 centime selon la part de tokens de sortie (les compteurs enregistrés sont des totaux ; la borne basse les facture tous au tarif d'entrée, la borne haute au tarif de sortie). Cloud Run, Workflows, Storage : moins d'un centime à eux trois. Sur un mois, la facture du traitement reste sous l'euro, quand une petite VM allumée en permanence coûte de l'ordre de la dizaine d'euros avant même le premier appel Gemini.
À 30 000 avis par nuit, avec la même proportion de tokens (environ 165 par avis pour le classement), il faut compter 5 millions de tokens, soit 1,5 à 12,5 $ par nuit selon la répartition entrée/sortie. C'est à ce volume que la prédiction par lot de Vertex AI, facturée moitié prix mais sans garantie de délai court, devient un vrai sujet (leçon 1).
L'inventaire avant suppression, puis la suppression vérifiée :
$ make cloud-status
scheduler.job lab-batch-nocturne-nuit
workflows.workflow lab-batch-nocturne
run.job lab-batch-nocturne-job
pubsub.topic lab-batch-nocturne-fin
pubsub.subscription lab-batch-nocturne-fin-sub
storage.bucket lab-batch-nocturne-<projet>
artifacts.repo lab-batch-nocturne
iam.sa lab-batch-nocturne-wf@<projet>.iam.gserviceaccount.com
iam.sa lab-batch-nocturne-sched@<projet>.iam.gserviceaccount.com
iam.sa lab-batch-nocturne-job@<projet>.iam.gserviceaccount.com
iam.binding roles/aiplatform.user
iam.binding roles/aiplatform.user
iam.binding roles/logging.logWriter
iam.binding roles/run.viewer
iam.binding roles/workflows.invoker
$ make cloud-down
==> Planification d'abord (plus aucun déclenchement), puis workflow et job
==> Pub/Sub, dépôt d'images, bucket (entrées, sorties, rapports, sources de build)
==> Droits au niveau projet, puis comptes de service
Aucune ressource du lab batch-nocturne dans le projet.
make cloud-down PROJECT=<projet> 16,49s user 2,14s system 37% cpu 49,910 total
$ make cloud-status
Aucune ressource du lab batch-nocturne dans le projet.Un balayage indépendant de cloud-status.sh (services et jobs Cloud Run de toutes les régions, workflows de toutes les régions, sujets, abonnements et instantanés Pub/Sub, buckets, dépôts Artifact Registry, métriques et puits de journaux, politiques d'alerte, comptes de service, politique IAM du projet, datasets BigQuery, secrets) n'a trouvé aucune ressource portant le préfixe du lab. Restent les journaux d'exécution, conservés 30 jours par le bucket de journaux par défaut puis effacés.
Ce qui casse en production
- Planification supprimée en dernier. Si le workflow est supprimé avant la planification, Scheduler continue à appeler une URL qui répond 404 chaque nuit, et échoue en silence. À l'inverse, supprimer le bucket avant le workflow peut faire échouer une exécution en cours, qui notifie un échec. Parade : planification, puis workflow et job, puis les ressources qu'ils utilisent.
- Coût caché des relances complètes. Sans les marqueurs de tranche, chaque relance repaie toute la nuit. Le chiffre à surveiller : les tokens d'une exécution de reprise comparés à ceux d'une nuit normale (716 contre 4 826 ici).
- Tokens de sortie non mesurés. Le projet enregistre des totaux ; impossible de dire précisément ce qu'a coûté une nuit. Parade : l'exercice ci-dessous, et un budget avec alerte sur le projet.
- Environnement de test oublié. Un
make cloud-uplancé pour une démonstration et jamais nettoyé fait une exécution par nuit, indéfiniment. Parade :make cloud-downen fin de séance, etmake cloud-statusdans la routine de la semaine.
La règle de décision
Mesurez les tokens à la source et faites-les remonter jusqu'à la notification ; c'est le seul poste qui grossit vraiment avec le volume. Comparez le coût d'une reprise à celui d'une nuit : s'ils sont proches, l'idempotence par tranche ne fonctionne pas. Supprimez dans l'ordre inverse du déclenchement, et prouvez la suppression par une liste vide, vérifiée deux fois.
Pratique
La facture Gemini du projet double un mois donné, alors que le volume d'avis est stable. Les notifications montrent des tokens_jobs normaux chaque nuit. Que cherchez-vous ?
Si chaque nuit coûte le même nombre de tokens, c'est le nombre de nuits qui a changé : des exécutions supplémentaires, ou un autre environnement qui tourne en parallèle (un cloud-up de démonstration oublié dans le même projet). La liste des exécutions du workflow et cloud-status le montrent en une minute. Un changement de tarif est possible, mais se vérifie sur la page publique, pas dans la facture du projet seul.
Vous devez supprimer l'environnement alors qu'une exécution est en cours (ACTIVE). Que faites-vous ?
Supprimer le bucket ou les comptes pendant l'exécution la fait échouer au milieu, avec des jobs encore lancés qui écrivent dans un bucket qui n'existe plus et une notification d'échec trompeuse. Couper d'abord la planification empêche toute nouvelle exécution ; attendre ou annuler termine proprement l'exécution en cours ; le nettoyage peut alors tout supprimer.
Exercice. Enregistrez séparément les tokens d'entrée et de sortie, de la tâche jusqu'à la notification, pour que le coût d'une nuit soit calculable exactement. Quels fichiers changent ?
Voir la solution
job/batch.py:GeminiClassifierrenvoie(classements, (meta.prompt_token_count, meta.candidates_token_count));classify_checkedetrun_taskcumulenttokens_entreeettokens_sortiedansstats;tests/test_batch.py: le faux Gemini renvoie un couple, les assertions vérifient les deux sommes ;infra/workflow.yaml: deux cumuls danscumuler(tokens_entree,tokens_sortie), et pour la synthèsegemini.usageMetadata.promptTokenCountetgemini.usageMetadata.candidatesTokenCountdansresume;e2e.py: le coût calculé dans le rapport,entree * 0.30e-6 + sortie * 2.50e-6, avec le tarif en constante datée.
Gardez le total aussi : c'est lui que comparent les tableaux existants.
- Je mesure les tokens à la source et je les fais remonter jusqu'à la notification.
- Je compare le coût d'une reprise à celui d'une nuit complète.
- J'extrapole un coût à un volume cible, avec ses bornes.
- Je supprime l'environnement dans le bon ordre et je prouve qu'il ne reste rien.