Fiabilité des requêtes asynchrones Fastly Compute
Problème
Les requêtes HTTP « fire-and-forget » depuis Fastly Compute@Edge utilisant send_async échouent silencieusement car le processus worker peut se terminer avant que la requête asynchrone atteigne le backend. De plus, les noms de backends référencés dans le code mais non configurés dans le tableau de bord Fastly causent des défaillances silencieuses faciles à manquer.
Contexte / Conditions de déclenchement
- Migration de fond, webhook ou notification déclenchés via
req.send_async(backend) - Le
PendingRequestretourné parsend_asyncest immédiatement abandonné (non attendu) - La réponse principale est envoyée au client, causant la terminaison du worker Compute
- L'erreur est avalée avec
let _ = ...oumatch ... { Err(_) => ... } - Fonctionne lors des tests locaux (
fastly compute serve) mais échoue en production - Le nom du backend dans le code ne correspond à aucun backend dans
fastly backend list --service-id
Solution
1. Utiliser le send() synchrone au lieu de send_async() pour les opérations critiques
// MAUVAIS : Fire-and-forget — le worker se termine avant que la requête se complète
match req.send_async(BACKEND) {
Ok(_pending) => {
// PendingRequest abandonné ici — la requête ne se complète probablement jamais !
Ok(())
}
Err(e) => { /* ... */ }
}
// BON : send synchrone — attend la réponse
match req.send(BACKEND) {
Ok(resp) => {
let status = resp.get_status();
if status.is_success() {
eprintln!("[MIGRATE] Success for {}", hash);
} else {
eprintln!("[MIGRATE] Backend returned {}", status);
}
Ok(())
}
Err(e) => {
eprintln!("[MIGRATE] Failed: {}", e);
Ok(()) // Ne pas échouer la requête principale
}
}
2. Quand le synchrone est trop lent, utiliser une couche de cache
Si une couche de cache VCL fait écran à Compute (service chaining), la latence supplémentaire du send synchrone n'affecte que les cache misses. Les requêtes suivantes frappent le cache. Cela rend le send synchrone acceptable pour des opérations comme les déclencheurs de migration.
3. Toujours vérifier l'existence du backend
# Lister les backends configurés sur le service
fastly backend list --service-id YOUR_SERVICE_ID --version latest
# Ajouter un backend manquant
fastly backend create --service-id YOUR_SERVICE_ID --version latest --autoclone \
--name cdn_divine --address cdn.divine.video --port 443 \
--use-ssl --ssl-sni-hostname cdn.divine.video --override-host cdn.divine.video
Vérifiez que chaque nom de backend dans votre code Rust (req.send("backend_name")) a une entrée correspondante dans le tableau de bord Fastly. La section [local_server.backends] de fastly.toml est réservée au dev local uniquement — elle ne crée PAS de backends en production.
4. Attention : le hostname de l'URL est cosmétique — le nom du backend décide du routage
Dans Fastly Compute, req.send(backend_name) / req.send_async(backend_name) ignore le hostname de l'URL pour le routage. La configuration du backend au tableau de bord (address, override_host, TLS SNI) détermine où la requête atterrit réellement. Cela signifie que deux services Cloud Run différents qui partagent une constante de backend livreront tous deux à l'adresse vers laquelle ce backend est câblé — et le call site qui semblait correct parce que son URL disait service-A.run.app frappera en réalité service-B.
Cela frappe le plus fort après une refactorisation globale. Exemple : une refactorisation remplace CLOUD_RUN_BACKEND par UPLOAD_SERVICE_BACKEND dans tout le fichier via sed. La plupart des call sites ciblaient légitimement le service d'upload, donc ils continuent à fonctionner. Mais un call site faisait POST https://divine-transcoder-XXXX.run.app/transcode et le rename le pointe vers le backend du service d'upload — la requête reçoit un 404 nginx du service d'upload, send_async retourne Ok (le backend a accepté la connexion), et la ligne de log eprintln!("[HLS] Triggered on-demand transcoding for {}", hash) est un faux positif. Le transcoder ne voit jamais la requête. Les vidéos s'accumulent dans un état « en cours » pendant des jours.
Comment attraper cette classe de bug :
-
Quand vous voyez un rename de constante de backend, grep pour chaque call site et vérifiez que le hostname de l'URL correspond à ce vers quoi le nouveau backend est configuré pour router :
grep -n "send(\|send_async(" src/*.rs grep -n "BACKEND: &str" src/*.rs # trouver les définitions de constantesTout call site où
format!("https://{}/...", SOMETHING_ELSE)ne correspond pas à l'address/override_hostdu backend au tableau de bord est un misroutage silencieux. -
Sonder la destination « erronée » directement de l'extérieur avec curl en utilisant un payload synthétique. Si vous recevez un 404 ou 405 de nginx/du mauvais service, vous avez trouvé un misroutage. Exemple :
curl -X POST https://upload.divine.video/transcode -H 'Content-Type: application/json' -d '{"hash":"0"*64}' # HTTP 404 <- mauvais service, backend misrouté -
Si vous possédez plus d'un service Cloud Run, définissez un backend par service (
transcoder_backend,upload_service,transcriber_backend) et ne les regroupez jamais juste parce que les deux sont*.run.app. Leoverride_hostsur chaque backend épingle sa destination quel que soit l'URL. -
Basculez les déclencheurs fire-and-forget auxquels vous tenez vers
send()synchrone au moins pendant l'investigation — une véritable réponse non-2xx du mauvais service rend le bug visible immédiatement au lieu de se cacher derrièresend_async.
5. Ne jamais avaler silencieusement les erreurs de backend pour les opérations importantes
// MAUVAIS : Défaillance silencieuse
let _ = trigger_migration(&hash, &source);
// BON : Logger l'erreur même si vous ne faites pas échouer la requête
if let Err(e) = trigger_migration(&hash, &source) {
eprintln!("[MIGRATE] Failed for {}: {}", hash, e);
}
Vérification
- Vérifiez les logs de Compute pour les messages de succès de migration/webhook
- Vérifiez que l'effet de bord s'est réellement produit (par ex., blob existe dans GCS après migration)
fastly backend list --service-id ID --version latestmontre tous les backends attendus
Exemple
Dans Divine Blossom, la migration à la demande de Bunny CDN vers GCS n'a jamais fonctionné parce que :
- Le backend
cdn_divinen'a jamais été configuré dans le tableau de bord Fastly (seulement dansfastly.toml) - Le
send_asyncpour la requête de migration Cloud Run a été abandonné avant la completion - Les deux erreurs ont été avalées silencieusement avec
let _ = ...
Correction : Ajout du backend cdn_divine via CLI, changement de send_async en send, ajout de logging.
Notes
fastly.toml[local_server.backends]configure seulement les backends pourfastly compute serve- Les backends de production doivent être configurés via le tableau de bord ou CLI (
fastly backend create) - Dans Fastly Compute, une fois que le corps de la réponse principale commence à streamer vers le client, le worker peut être terminé à tout moment — les requêtes asynchrones en vol peuvent être annulées
- Si vous avez besoin d'un vrai fire-and-forget, envisagez d'appeler une queue externe (Cloud Tasks, PubSub) au lieu du HTTP direct