Beedeez · 2020 – 2023
Téléchargement groupé des certificats de formation
Générer en tâche de fond les certificats de tous les apprenants d'un compte, puis les livrer en un seul zip grâce au polling.
- TypeScript
- Node.js
- BullMQ
- Redis
- AWS S3
- React Native
- React Native Web
Contexte
Chez Beedeez, les apprenants obtenaient un certificat à la fin d'un parcours de formation, selon leurs réponses aux différents quiz. Ces certificats, au format PDF, étaient générés côté serveur et chaque apprenant pouvait les consulter et les télécharger. Les administrateurs pouvaient, eux, télécharger d'un coup les certificats de tous leurs utilisateurs.
Le système devait tenir un téléchargement massif de certificats. Tout générer pendant une seule requête HTTP n'était pas envisageable : la requête risquait d'expirer et une perte de connexion en cours de route aurait fait perdre tout le travail. Côté backend, il fallait aussi pouvoir générer et stocker un grand nombre de certificats avant de les envoyer.
Approche
J'ai séparé la demande de téléchargement de la génération elle-même : le serveur lance le travail en tâche de fond et répond immédiatement, puis le navigateur vient régulièrement lui demander où en est l'export.
Demande de téléchargement
Un administrateur, depuis le back-office
Navigateur
Lancement du polling↻
Demande le statut de l'export à intervalle régulier
Réponse : « en cours »
Le polling continue tant que le zip n'est pas prêt
Serveur
Queue BullMQRedis
Un job par paquet de certificats
Workers×N
Génèrent les certificats de chaque paquet
Certificats sur S3
Stockés temporairement, le temps de tout générer
Création du zip
Une fois tous les jobs terminés, déposé lui aussi sur S3
Réponse : « terminé »
Le polling suivant reçoit une URL signée vers le zip et s'arrête
Téléchargement du zip
Directement depuis S3, sans passer par notre serveur
Côté backend : une queue BullMQ
Lorsqu'un administrateur demandait le téléchargement, le serveur ajoutait des jobs dans une queue BullMQ (qui s'appuie sur Redis), chaque job correspondant à un paquet de certificats. Découper le travail en paquets évitait d'avoir un seul job très long et gourmand en mémoire pour les gros comptes.
Un worker traitait ensuite ces jobs, générait les PDF et les stockait de manière temporaire sur AWS S3, le temps de générer l'ensemble. Une fois tous les jobs terminés, les certificats étaient rassemblés dans un zip, déposé lui aussi sur S3, et l'export passait au statut « terminé ».
Côté frontend : le polling
Côté frontend, j'ai mis en place en TypeScript un système de polling. Le client envoyait une requête au backend à intervalle régulier pour connaître le statut de l'export. Une fois le zip prêt, la réponse contenait une URL signée S3. Ce lien temporaire permettait de télécharger le zip directement depuis S3, sans le faire transiter par notre serveur. Le polling s'arrêtait au bout d'un certain délai (timeout), pour ne pas interroger le serveur indéfiniment si un export restait bloqué.
Comme la génération tourne côté serveur, indépendamment de la connexion du client, une coupure réseau n'interrompt pas le travail.
Résultat
Les administrateurs pouvaient récupérer en un seul zip les certificats de tous leurs utilisateurs sans dépendre d'une requête unique qui risquait d'expirer. Le découpage en paquets permettait d'améliorer les performances en cas de génération massive de données.
Ce que j'en retiens
- Dès qu'un traitement peut dépasser la durée d'une requête HTTP, mieux vaut le confier à une queue et exposer un statut. Le travail ne dépend alors plus de la connexion du client.
- Pour générer un grand volume de fichiers, découper le travail en paquets permet d'améliorer les performances.