
Pour quelqu'un qui recherche un programme simple et rapide qui prend un csv, appelle l'API de validation des destinataires et produit un CSV, ce programme est fait pour vous.
Lors de la création d'applications de messagerie, les développeurs doivent souvent intégrer plusieurs services et API. Comprendre les fondamentaux de l'API email dans l'infrastructure cloud fournit la base pour créer des outils robustes comme le système de validation en masse que nous allons créer dans ce guide.
Une des questions que nous recevons occasionnellement est, comment puis-je valider en masse des listes d'e-mails avec la validation des destinataires? Il y a deux options ici, l'une est de télécharger un fichier via l'interface utilisateur de SparkPost pour la validation, et l'autre est de faire des appels individuels par e-mail à l'API (puisque l'API est une validation d'e-mail unique).
La première option fonctionne bien mais a une limitation de 20 Mo (environ 500 000 adresses). Que faire si quelqu'un a une liste d'e-mails contenant des millions d'adresses ? Cela pourrait signifier diviser cela en milliers de téléchargements de fichiers CSV.
Puisque le téléchargement de milliers de fichiers CSV semble un peu farfelu, j'ai pris ce cas d'utilisation et j'ai commencé à me demander quelle vitesse je pourrais obtenir avec l'API. Dans cet article de blog, je vais expliquer ce que j'ai essayé et comment je suis finalement arrivé à un programme qui pourrait réaliser environ 100 000 validations en 55 secondes (alors que dans l'interface utilisateur, j'obtenais environ 100 000 validations en 1 minute 10 secondes). Et bien que cela prenne encore environ 100 heures pour réaliser environ 654 millions de validations, ce script peut fonctionner en arrière-plan, économisant ainsi un temps considérable.
La version finale de ce programme peut être trouvée ici.
My first mistake: utiliser Python
Python est l'un de mes langages de programmation préférés. Il excelle dans de nombreux domaines et est incroyablement simple. Cependant, un domaine dans lequel il n'excelle pas est celui des processus concurrents. Bien que python ait la capacité d'exécuter des fonctions asynchrones, il possède ce qu'on appelle le verrou global de l'interpréteur Python ou GIL.
« Le verrou global de l'interpréteur Python ou GIL, en termes simples, est un mutex (ou un verrou) qui permet à un seul thread de maintenir le contrôle de l'interpréteur Python.
Cela signifie qu'un seul thread peut être en cours d'exécution à tout moment. L'impact du GIL n'est pas visible pour les développeurs qui exécutent des programmes monothreadés, mais il peut être un goulot d'étranglement de performance dans le code à encombrement CPU et multi-thread.
Étant donné que le verrou global de l'interpréteur (GIL) permet à un seul thread de s'exécuter à la fois, même sur les systèmes multicœurs, il a acquis une réputation de « fonction célèbre » de Python (voir l'article de Real Python sur le GIL).
Au début, je n'étais pas conscient du GIL, donc j'ai commencé à programmer en python. À la fin, même si mon programme était asynchrone, il était coincé, et peu importe le nombre de threads que j'ajoutais, je n'obtenais toujours qu'environ 12 à 15 itérations par seconde.
La partie principale de la fonction asynchrone en Python peut être vue ci-dessous :
Donc, j'ai abandonné l'utilisation de Python et je suis retourné à la planche à dessin...
Je me suis décidé à utiliser NodeJS en raison de sa capacité à effectuer des opérations d'entrée/sortie non bloquantes extrêmement bien. Une autre option excellente pour gérer le traitement asynchrone de l'API est de construire des consommateurs de webhook sans serveur avec Azure Functions, qui peuvent gérer efficacement des charges de travail variables. Je suis également assez familier avec la programmation en NodeJS.
En utilisant les aspects asynchrones de Node.js, cette approche a bien fonctionné. Pour plus de détails sur la programmation asynchrone dans Node.js, voir le guide de RisingStack sur la programmation asynchrone dans Node.js.
Ma deuxième erreur : essayer de lire le fichier en mémoire
Décomposition du code final
Après avoir lu et validé les arguments du terminal, j'exécute le code suivant. Tout d'abord, je lis le fichier CSV des e-mails et compte chaque ligne. Cette fonction a deux objectifs : 1) me permettre de reporter avec précision la progression du fichier [comme nous le verrons plus tard], et 2) me permettre d'arrêter un chronomètre lorsque le nombre d'e-mails dans le fichier équivaut aux validations terminées. J'ai ajouté un chronomètre afin de pouvoir exécuter des bancs d'essai et m'assurer d'obtenir de bons résultats.
Je fais ensuite appel à la fonction validateRecipients. Notez que cette fonction est asynchrone. Après avoir vérifié que le infile et le outfile sont au format CSV, j'écris une ligne d'en-tête et démarre un chronomètre de programme en utilisant la bibliothèque JSDOM.
Le script suivant constitue vraiment l'essentiel du programme, donc je vais le découper et expliquer ce qui se passe. Pour chaque ligne du infile :
Prendre asynchrone cette ligne et appeler l'API de validation des destinataires.
Ensuite, sur la réponse
Ajouter l'e-mail au JSON (afin de pouvoir imprimer l'e-mail dans le CSV)
Vérifier si la raison est nulle, et si c'est le cas, renseigner une valeur vide (c'est pour que le format CSV soit cohérent, car dans certains cas, une raison est donnée dans la réponse)
Définir les options et les clés pour le module json2csv.
Convertir le JSON en CSV et produire un fichier (en utilisant json2csv)
Écrire la progression dans le terminal
Enfin, si le nombre d'e-mails dans le fichier = validations terminées, arrêter le chronomètre et imprimer les résultats
Un dernier problème que j'ai trouvé est que, bien que cela fonctionnait très bien sur Mac, j'ai rencontré l'erreur suivante sous Windows après environ 10 000 validations :
Erreur : connect ENOBUFS XX.XX.XXX.XXX:443 – Local (indéfini:indéfini) avec l'e-mail XXXXXXX@XXXXXXXXXX.XXX
Après avoir effectué des recherches supplémentaires, il semble s'agir d'un problème avec le pool de connexions du client HTTP NodeJS qui ne réutilise pas les connexions. J'ai trouvé cet article Stackoverflow sur le problème, et après des recherches plus approfondies, j'ai trouvé une bonne configuration par défaut pour la bibliothèque axios qui a résolu ce problème. Je ne suis toujours pas certain pourquoi ce problème ne survient que sous Windows et non sur Mac.
Étapes suivantes
Pour quelqu'un qui recherche un programme simple et rapide qui accepte un fichier CSV, appelle l'API de validation des destinataires et génère un CSV, ce programme est fait pour vous.
Voici quelques ajouts possibles à ce programme :
Construire une interface utilisateur ou un front-end plus facile à utiliser
Meilleure gestion des erreurs et des réessais, car si pour une raison quelconque l'API génère une erreur, le programme ne relance actuellement pas l'appel
Envisager la mise en œuvre en tant que fonction Azure sans serveur pour un dimensionnement automatique et une gestion réduite de l'infrastructure
Je serais également curieux de voir si des résultats plus rapides pourraient être obtenus avec un autre langage, tel que Golang ou Erlang/Elixir. Au-delà du choix de langage, les limites de l'infrastructure peuvent également affecter les performances - nous avons appris cela de première main lorsque nous avons atteint les limites DNS non documentées dans AWS qui ont affecté nos systèmes de traitement des e-mails à haut volume.
Pour les développeurs intéressés par la combinaison du traitement API avec les outils de flux de travail visuels, découvrez comment intégrer Flow Builder avec Google Cloud Functions pour des flux de travail d'automatisation sans code.
N'hésitez pas à me fournir tout retour ou suggestions pour l'expansion de ce projet.