uncloth.app Guides

Passer à l'échelle la génération d'images : files, idempotence et retries

La génération d'images casse les hypothèses sur lesquelles reposent la plupart des architectures web. Les requêtes durent des dizaines de secondes et non des millisecondes, la capacité est réellement finie parce qu'elle est physique, et une requête échouée a déjà coûté de l'argent. Les patterns qui maintiennent tout cela correct sont bien connus — ils ne sont simplement pas ceux qu'un backend CRUD classique possède déjà.

La file, c'est la conception

Le premier réflexe est d'appeler l'API directement depuis le handler de requête. Cela marche en développement et échoue dès le premier trafic réel, parce qu'il n'y a rien entre la demande et la capacité : un pic devient un mur de timeouts, et il ne reste aucune trace de ce qui était en cours au redémarrage du process.

Mettez une file durable au milieu. « Durable » est le mot clé : une liste en mémoire perd tous les jobs en cours à chaque déploiement, et les déploiements arrivent en plein trafic. Une table en base fait parfaitement l'affaire à cette échelle et vous donne, en prime, un état récupérable.

Le backpressure est une fonctionnalité

Une fois la file en place, vous pouvez décider combien de jobs sont autorisés en vol simultanément, et cette limite est une décision produit plutôt qu'un détail technique. Trop haute, tout est lent pour tout le monde. Trop basse, la capacité reste inactive pendant que la file s'allonge. L'essentiel est que la limite existe et soit appliquée à un seul endroit, pour qu'un pic de trafic allonge la file au lieu de dégrader toutes les requêtes.

L'idempotence et l'échec ambigu

Le mode de défaillance qui coûte de l'argent n'est pas une réponse d'erreur — les erreurs sont faciles. C'est le cas ambigu : la requête est sortie de votre process, la connexion a coupé, et vous ne pouvez pas savoir si le job a été créé. Réessayez et vous avez peut-être payé deux fois. Ne réessayez pas et un utilisateur a peut-être payé pour rien.

Une clé d'idempotence résout cela en rendant le retry sûr : la même clé renvoie le job d'origine au lieu d'en créer un second. Les règles sont simples et faciles à rater — générez la clé au moment où l'utilisateur agit, pas au moment de l'appel HTTP ; stockez-la avec l'enregistrement du job ; réutilisez-la à chaque nouvelle tentative de cette action.

# la même clé pour chaque retry d'une seule action utilisateur
KEY=$(uuidgen)
curl -X POST https://api.example/api/v1/jobs \
  -H "X-API-Key: $API_KEY" -H "Idempotency-Key: $KEY" \
  -F image=@photo.jpg -F feature=undress -F consent=confirmed

Ce qu'il ne faut pas réessayer automatiquement

Il existe une catégorie d'échecs où le bon geste est de s'arrêter. Si votre process est mort entre l'envoi d'une soumission et l'enregistrement de la réponse, vous ignorez réellement ce qui s'est passé — et sans clé pour dédupliquer, un retry automatique est un pile ou face qui peut facturer deux fois. Marquez ces jobs comme nécessitant une intervention plutôt que de les rejouer à l'aveugle. Un job visiblement bloqué vaut mieux qu'un doublon facturé en silence.

La réconciliation : la boucle qui vous sauve

Les événements en direct sont pratiques et peu fiables. Les sockets tombent, les process redémarrent, et le seul message qui comptait arrive quand personne n'écoute. Le correctif est une boucle de fond qui interroge périodiquement l'API sur chaque job que votre base considère encore actif, et met à jour l'état d'après la réponse.

Cette seule boucle absorbe toute une classe de bugs. Les événements manqués cessent d'avoir de l'importance. Un redémarrage en pleine génération aussi. Chaque job converge vers son état réel en l'espace d'un intervalle de réconciliation, quoi qu'il soit arrivé à la connexion entre-temps.

La progression sans marteler votre base

La génération émet de la progression fréquemment — potentiellement plusieurs fois par seconde et par job. Écrire chaque tick en base multiplie votre charge d'écriture sans apporter la moindre information supplémentaire. Limitez la persistance à environ une fois par seconde, écrivez toujours l'état final, puis diffusez les valeurs en direct aux clients connectés sans toucher au stockage.

Une checklist qui tient la route

  • File durable entre les utilisateurs et l'API, récupérable après redémarrage.
  • Une limite explicite de jobs en vol, appliquée à un seul endroit.
  • Clé d'idempotence générée par action utilisateur, stockée, et réutilisée au retry.
  • Échecs d'envoi ambigus signalés plutôt que rejoués automatiquement.
  • Une boucle de réconciliation qui fait converger l'état malgré les événements manqués.
  • Écritures de progression limitées, progression en direct via la socket.

Rien de tout cela n'est propre à la génération d'images — c'est de l'hygiène ordinaire de systèmes distribués. Cela devient simplement visible ici, parce que chaque unité de travail est assez lente et assez coûteuse pour que les raccourcis habituels cessent de passer inaperçus.

Questions fréquentes

Ai-je besoin d'un broker de messages ?

En général pas à cette échelle. Une table en base avec une requête de réservation vous donne la durabilité et un historique d'état récupérable, sans pièce mobile supplémentaire.

Combien de jobs autoriser en vol ?

Commencez bas, mesurez l'attente en file par rapport au temps de traitement, et augmentez jusqu'à ce que la latence cesse de s'améliorer. Le chiffre importe moins que l'existence d'une limite unique et appliquée.

Les jobs échoués doivent-ils être rejoués automatiquement ?

Les erreurs claires, oui. Les échecs d'envoi ambigus, où vous ne savez pas si le travail a été créé, non — signalez-les, car un retry aveugle peut dupliquer une génération facturée.

Développez avec uncloth.app

Un seul endpoint REST, onze presets, les résultats renvoyés à votre backend. Dites-nous ce que vous construisez et nous vous enverrons les accès.

Demander un accès

Tous les guides