
Voor iemand die op zoek is naar een eenvoudig en snel programma dat een csv-bestand verwerkt, de ontvanger-validatie API aanroept en een CSV oplevert, is dit programma voor jou.
Bij het bouwen van e-mailapplicaties moeten ontwikkelaars vaak meerdere services en API's integreren. Inzicht in de basisprincipes van e-mail-API in cloudinfrastructuur biedt de basis voor het bouwen van robuuste tools zoals het bulkvalidatiesysteem dat we in deze gids zullen maken.
Een van de vragen die we af en toe ontvangen is, hoe kan ik e-maillijsten bulk valideren met ontvanger validatie? Er zijn hier twee opties, één is om een bestand te uploaden via de SparkPost UI voor validatie, en de andere is om individuele oproepen per e-mail naar de API te doen (aangezien de API een enkele e-mail validatie is).
De eerste optie werkt prima, maar heeft een limiet van 20Mb (ongeveer 500.000 adressen). Wat als iemand een e-maillijst heeft met miljoenen adressen? Het kan betekenen dat dat moet worden opgesplitst in duizenden CSV-bestandsuploads.
Aangezien het uploaden van duizenden CSV-bestanden een beetje vergezocht lijkt, nam ik dat gebruiksscenario en begon ik me af te vragen hoe snel ik de API kon laten draaien. In deze blogpost zal ik uitleggen wat ik heb geprobeerd en hoe ik uiteindelijk tot een programma kwam dat ongeveer 100.000 validaties in 55 seconden kon krijgen (terwijl ik in de UI rond de 100.000 validaties in 1 minuut en 10 seconden kreeg). En hoewel dit nog steeds ongeveer 100 uur zou duren om ongeveer 654 miljoen validaties uit te voeren, kan dit script op de achtergrond draaien en aanzienlijk tijd besparen.
De laatste versie van dit programma is te vinden hier.
Mijn eerste fout: het gebruik van Python
Python is een van mijn favoriete programmeertalen. Het blinkt uit in veel gebieden en is ongelooflijk eenvoudig. Echter, een gebied waar het niet in uitblinkt, is gelijktijdige processen. Hoewel Python de mogelijkheid heeft om asynchrone functies uit te voeren, heeft het iets dat bekend staat als The Python Global Interpreter Lock of GIL.
“The Python Global Interpreter Lock of GIL, in eenvoudige woorden, is een mutex (of een slot) dat slechts één thread toestaat om de controle van de Python-interpreter te houden.
Dit betekent dat slechts één thread zich op elk moment in een uitvoeringsstaat kan bevinden. De impact van de GIL is niet zichtbaar voor ontwikkelaars die enkelvoudige threading-programma's uitvoeren, maar het kan een prestatieknelpunt zijn in CPU-gebonden en multithreaded code.
Aangezien de Global Interpreter Lock (GIL) slechts één thread tegelijkertijd toestaat om uit te voeren, zelfs op multi-core systemen, heeft het de reputatie als een "beruchte" eigenschap van Python (zie Real Python’s artikel over de GIL).
In het begin was ik niet op de hoogte van de GIL, dus begon ik in Python te programmeren. Aan het einde, hoewel mijn programma asynchroon was, raakte het geblokkeerd en ongeacht hoeveel threads ik toevoegde, kreeg ik nog steeds slechts ongeveer 12-15 iteraties per seconde.
Het hoofdgedeelte van de asynchrone functie in Python is hieronder te zien:
Dus stopte ik met het gebruik van Python en ging terug naar de tekentafel…
Ik koos ervoor om NodeJS te gebruiken vanwege zijn vermogen om non-blocking i/o-operaties extreem goed uit te voeren. Een andere uitstekende optie voor het verwerken van asynchrone API-processen is het bouwen van serverloze webhook-consumers met Azure Functions, die variabele workloads efficiënt kunnen verwerken. Ik ben ook behoorlijk bekend met programmeren in NodeJS.
Door gebruik te maken van de asynchrone aspecten van Node.js, werkte deze aanpak goed. Voor meer details over asynchrone programmering in Node.js, zie RisingStack’s gids voor asynchrone programmering in Node.js.
Mijn tweede fout: proberen het bestand in het geheugen te lezen
Het uiteindelijke code ontleden
Nadat ik de terminalargumenten heb gelezen en gevalideerd, voer ik de volgende code uit. Eerst lees ik het CSV-bestand met e-mails in en tel ik elke regel. Er zijn twee doelstellingen van deze functie, 1) het stelt me in staat om nauwkeurig te rapporteren over de voortgang van het bestand [zoals we later zullen zien], en 2) het stelt me in staat om een timer te stoppen wanneer het aantal e-mails in het bestand gelijk is aan voltooide validaties. Ik heb een timer toegevoegd zodat ik benchmarks kan uitvoeren en ervoor kan zorgen dat ik goede resultaten krijg.
Daarna roep ik de functie validateRecipients aan. Let op, deze functie is asynchroon. Nadat ik heb gevalideerd dat het in- en uitvoerbestand CSV zijn, schrijf ik een kopregel en start ik een programma-timer met behulp van de JSDOM-bibliotheek.
Het volgende script is eigenlijk de kern van het programma, dus ik zal het opsplitsen en uitleggen wat er gebeurt. Voor elke regel van het infile:
Neem die regel asynchroon en roep de ontvanger validatie API aan.
Dan, bij de respons
Voeg de e-mail toe aan de JSON (om de e-mail in de CSV te kunnen afdrukken)
Valideer of reason null is, en als dat zo is, vul dan een lege waarde in (dit is zodat het CSV-formaat consistent is, omdat in sommige gevallen reason in de respons wordt gegeven)
Stel de opties en sleutels in voor de json2csv-module.
Zet de JSON om naar CSV en voer het uit (gebruikmakend van json2csv)
Schrijf voortgang in de terminal
Ten slotte, als het aantal e-mails in het bestand = voltooide validaties, stop de timer en druk de resultaten af
Een laatste probleem dat ik ontdekte was dat hoewel dit prima werkte op Mac, ik de volgende fout tegenkwam bij het gebruik van Windows na ongeveer 10.000 validaties:
Fout: connect ENOBUFS XX.XX.XXX.XXX:443 – Lokaal (undefined:undefined) met e-mail XXXXXXX@XXXXXXXXXX.XXX
Na verder onderzoek blijkt het een probleem te zijn met de NodeJS HTTP-client connectiepool die geen verbindingen hergebruikt. Ik vond dit Stackoverflow-artikel over het probleem, en na verder graven, vond ik een goede standaardconfiguratie voor de axios-bibliotheek die dit probleem oploste. Ik weet nog steeds niet zeker waarom dit probleem alleen op Windows en niet op Mac optreedt.
Volgende Stappen
Voor iemand die op zoek is naar een eenvoudig snel programma dat een csv als invoer neemt, de ontvangersvalidatie-API aanroept en een CSV uitvoert, is dit programma voor jou.
Enkele aanvullingen op dit programma zouden het volgende zijn:
Bouw een front-end of eenvoudigere UI voor gebruik
Beter fout- en retrybeheer omdat het programma momenteel de oproep niet opnieuw uitvoert als de API om een of andere reden een fout geeft
Overweeg implementatie als een serverless Azure Function voor automatische schaalvergroting en verminderd infrastructuurbeheer
Ik ben ook benieuwd of snellere resultaten zouden kunnen worden bereikt met een andere taal, zoals Golang of Erlang/Elixir. Buiten de taalkeuze kunnen infrastructuurbeperkingen ook de prestaties beïnvloeden - dit hebben we uit de eerste hand geleerd toen we ongedocumenteerde DNS-beperkingen in AWS tegenkwamen die onze systemen voor het verwerken van e-mails met een hoog volume beïnvloedden.
Voor ontwikkelaars die geïnteresseerd zijn in het combineren van API-verwerking met visuele workflowtools, bekijk hoe u Flow Builder kunt integreren met Google Cloud Functions voor no-code automatiseringsworkflows.
Voel je vrij om mij feedback of suggesties te geven voor het uitbreiden van dit project.