Bereik

Grow

Manage

Automate

Bereik

Grow

Manage

Automate

Een Bulk Asynchroon Bird Ontvanger Validatie Tool Bouwen

E-mail

1 min read

Een Bulk Asynchroon Bird Ontvanger Validatie Tool Bouwen

E-mail

1 min read

Een Bulk Asynchroon Bird Ontvanger Validatie Tool Bouwen

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.

Een van de vragen die we af en toe ontvangen is, hoe kan ik e-maillijsten bulkvalideren met ontvangervalidatie? Er zijn hier twee opties: de ene is een bestand uploaden via de SparkPost UI voor validatie, en de andere is om individuele oproepen per e-mail naar de API te maken (aangezien de API eenmalige e-mailvalidatie 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? Dat zou betekenen dat het zou moeten worden opgesplitst in duizenden CSV-bestandsuploads.

Aangezien het uploaden van duizenden CSV-bestanden een beetje vergezocht lijkt, nam ik dat gebruiksgeval 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 rond de 100.000 validaties in 55 seconden kon krijgen (terwijl in de UI ik rond de 100.000 validaties in 1 minuut en 10 seconden kreeg). Hoewel dit nog steeds ongeveer 100 uur zou duren om ongeveer 654 miljoen validaties af te ronden, kan dit script op de achtergrond draaien, wat aanzienlijke tijd bespaart.

De definitieve 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 waarin het niet uitblinkt is gelijktijdige processen. Hoewel Python de mogelijkheid heeft om asynchrone functies uit te voeren, heeft het iets dat bekend staat als de Python Global Interpreter Lock of GIL.

“De Python Global Interpreter Lock of GIL, in eenvoudige woorden, is een mutex (of een lock) die slechts één thread toestaat de controle van de Python-interpreter vast te houden.

Dit betekent dat slechts één thread tegelijkertijd in een uitvoeringsstatus kan zijn. De impact van de GIL is niet zichtbaar voor ontwikkelaars die enkelvoudig getreadde programma's uitvoeren, maar het kan een prestatieknelpunt zijn in CPU-belastende en multi-threaded code.

Aangezien de GIL slechts één thread toestaat om op een bepaald ogenblik uit te voeren, zelfs in een multi-threaded architectuur met meer dan één CPU-kern, heeft de GIL de reputatie opgebouwd als een “beruchte” eigenschap van Python.” (https://realpython.com/python-gil/)”

In eerste instantie was ik niet op de hoogte van de GIL, dus begon ik met programmeren in Python. Aan het einde, ook al was mijn programma asynchroon, werd het vergrendeld, en hoeveel threads ik ook toevoegde, ik kreeg nog steeds maar ongeveer 12-15 iteraties per seconde.

Het belangrijkste deel van de asynchrone functie in Python is hieronder te zien:

async def validateRecipients(f, fh, apiKey, snooze, count): h = {'Authorization': apiKey, 'Accept': 'application/json'} with tqdm(total=count) as pbar: async with aiohttp.ClientSession() as session: for address in f: for i in address: thisReq = requests.compat.urljoin(url, i) async with session.get(thisReq,headers=h, ssl=False) as resp: content = await resp.json() row = content['results'] row['email'] = i fh.writerow(row) pbar.update(1)

Dus ik stopte met het gebruik van Python en begon opnieuw met het ontwerp...

Ik koos ervoor om NodeJS te gebruiken vanwege zijn vermogen om non-blocking i/o-bewerkingen zeer goed uit te voeren. Ik ben ook vrij bekend met programmeren in NodeJS.

Door gebruik te maken van de asynchrone aspecten van NodeJS, werkte dit uiteindelijk goed. Voor meer details over asynchroon programmeren in NodeJS, zie https://blog.risingstack.com/node-hero-async-programming-in-node-js/

Mijn tweede fout: proberen het bestand in het geheugen te lezen

Mijn oorspronkelijke idee was als volgt:

Flowchart illustrating the process of validating a CSV list of emails, starting with ingestion, format checking, asynchronous API validation, result aggregation, and concluding with outputting to a CSV file.


Eerst een CSV-lijst met e-mails inlezen. Ten tweede de e-mails in een array laden en controleren of ze in het juiste formaat zijn. Ten derde de ontvanger-validatie-API asynchroon aanroepen. Ten vierde wachten op de resultaten en deze in een variabele laden. En ten slotte deze variabele naar een CSV-bestand uitvoeren.

Dit werkte erg goed voor kleinere bestanden. Het probleem ontstond toen ik 100.000 e-mails probeerde te verwerken. Het programma liep vast bij ongeveer 12.000 validaties. Met de hulp van een van onze front-end ontwikkelaars ontdekte ik dat het probleem lag bij het laden van alle resultaten in een variabele (en dus snel zonder geheugen kwam te zitten). Als je de eerste iteratie van dit programma wilt zien, heb ik deze hier gelinkt: Version 1 (NOT RECOMMENDED).


Flowchart illustrating an email processing workflow, showing steps from ingesting a CSV list of emails to outputting results to a CSV file, with asynchronous validation via an API.


Eerst een CSV-lijst met e-mails inlezen. Ten tweede het aantal e-mails in het bestand tellen voor rapportagedoeleinden. Ten derde, terwijl elke regel asynchroon wordt gelezen, de ontvanger-validatie-API aanroepen en de resultaten naar een CSV-bestand uitvoeren.

Dus, voor elke ingelezen regel roep ik de API aan en schrijf ik de resultaten asynchroon weg om geen van deze gegevens in het langetermijngeheugen te bewaren. Ik heb ook de e-mailsyntaxcontrole verwijderd na overleg met het ontvanger-validatieteam, aangezien ze me vertelden dat ontvanger-validatie al ingebouwde controles heeft om te controleren of een e-mail geldig is of niet.

Mijn oorspronkelijke idee was als volgt:

Flowchart illustrating the process of validating a CSV list of emails, starting with ingestion, format checking, asynchronous API validation, result aggregation, and concluding with outputting to a CSV file.


Eerst een CSV-lijst met e-mails inlezen. Ten tweede de e-mails in een array laden en controleren of ze in het juiste formaat zijn. Ten derde de ontvanger-validatie-API asynchroon aanroepen. Ten vierde wachten op de resultaten en deze in een variabele laden. En ten slotte deze variabele naar een CSV-bestand uitvoeren.

Dit werkte erg goed voor kleinere bestanden. Het probleem ontstond toen ik 100.000 e-mails probeerde te verwerken. Het programma liep vast bij ongeveer 12.000 validaties. Met de hulp van een van onze front-end ontwikkelaars ontdekte ik dat het probleem lag bij het laden van alle resultaten in een variabele (en dus snel zonder geheugen kwam te zitten). Als je de eerste iteratie van dit programma wilt zien, heb ik deze hier gelinkt: Version 1 (NOT RECOMMENDED).


Flowchart illustrating an email processing workflow, showing steps from ingesting a CSV list of emails to outputting results to a CSV file, with asynchronous validation via an API.


Eerst een CSV-lijst met e-mails inlezen. Ten tweede het aantal e-mails in het bestand tellen voor rapportagedoeleinden. Ten derde, terwijl elke regel asynchroon wordt gelezen, de ontvanger-validatie-API aanroepen en de resultaten naar een CSV-bestand uitvoeren.

Dus, voor elke ingelezen regel roep ik de API aan en schrijf ik de resultaten asynchroon weg om geen van deze gegevens in het langetermijngeheugen te bewaren. Ik heb ook de e-mailsyntaxcontrole verwijderd na overleg met het ontvanger-validatieteam, aangezien ze me vertelden dat ontvanger-validatie al ingebouwde controles heeft om te controleren of een e-mail geldig is of niet.

Mijn oorspronkelijke idee was als volgt:

Flowchart illustrating the process of validating a CSV list of emails, starting with ingestion, format checking, asynchronous API validation, result aggregation, and concluding with outputting to a CSV file.


Eerst een CSV-lijst met e-mails inlezen. Ten tweede de e-mails in een array laden en controleren of ze in het juiste formaat zijn. Ten derde de ontvanger-validatie-API asynchroon aanroepen. Ten vierde wachten op de resultaten en deze in een variabele laden. En ten slotte deze variabele naar een CSV-bestand uitvoeren.

Dit werkte erg goed voor kleinere bestanden. Het probleem ontstond toen ik 100.000 e-mails probeerde te verwerken. Het programma liep vast bij ongeveer 12.000 validaties. Met de hulp van een van onze front-end ontwikkelaars ontdekte ik dat het probleem lag bij het laden van alle resultaten in een variabele (en dus snel zonder geheugen kwam te zitten). Als je de eerste iteratie van dit programma wilt zien, heb ik deze hier gelinkt: Version 1 (NOT RECOMMENDED).


Flowchart illustrating an email processing workflow, showing steps from ingesting a CSV list of emails to outputting results to a CSV file, with asynchronous validation via an API.


Eerst een CSV-lijst met e-mails inlezen. Ten tweede het aantal e-mails in het bestand tellen voor rapportagedoeleinden. Ten derde, terwijl elke regel asynchroon wordt gelezen, de ontvanger-validatie-API aanroepen en de resultaten naar een CSV-bestand uitvoeren.

Dus, voor elke ingelezen regel roep ik de API aan en schrijf ik de resultaten asynchroon weg om geen van deze gegevens in het langetermijngeheugen te bewaren. Ik heb ook de e-mailsyntaxcontrole verwijderd na overleg met het ontvanger-validatieteam, aangezien ze me vertelden dat ontvanger-validatie al ingebouwde controles heeft om te controleren of een e-mail geldig is of niet.

Het uiteindelijke code ontleden

Na het lezen en valideren van de terminalargumenten voer ik de volgende code uit. Eerst lees ik het CSV-bestand met e-mails in en tel ik elke regel. Er zijn twee doelen van deze functie, 1) het stelt me in staat om nauwkeurig over de voortgang van het bestand te rapporteren [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 de voltooide validaties. Ik heb een timer toegevoegd zodat ik benchmarks kan uitvoeren en ervoor kan zorgen dat ik goede resultaten behaal.

let count = 0; //Lijn tellen require("fs") .createReadStream(myArgs[1]) .on("data", function (chunk) { for (let i = 0; i < chunk.length; ++i) if (chunk[i] == 10) count++; }) //Leest het invoerbestand en verhoogt de telling voor elke regel .on("close", function () { //Aan het einde van het invoerbestand, nadat alle regels zijn geteld, de functievalideringsfunctie validateRecipients.runnen validateRecipients(count, myArgs); });


 Vervolgens roep ik de validateRecipients-functie aan. Merk op dat deze functie asynchroon is. Na validatie dat het invoer- en uitvoerbestand CSV-bestanden zijn, schrijf ik een kopregel en start ik een programmatimer met behulp van de JSDOM-bibliotheek.

async function validateRecipients(email_count, myArgs) { if ( //Als zowel het invoer- als uitvoerbestand in .csv formaat is extname(myArgs[1]).toLowerCase() == ".csv" && extname(myArgs[3]).toLowerCase() == ".csv" ) { let completed = 0; //Teller voor elke API-oproep email_count++; //Lijnteller retourneert #lines - 1, dit wordt gedaan om het aantal regels te corrigeren //Start een timer const { window } = new JSDOM(); const start = window.performance.now(); const output = fs.createWriteStream(myArgs[3]); //Uitvoerbestand output.write( "Email,Valid,Result,Reason,Is_Role,Is_Disposable,Is_Free,Delivery_Confidence\n" ); //Schrijf de koppen in het uitvoerbestand


Het volgende script is eigenlijk de kern van het programma, dus ik zal het opbreken en uitleggen wat er gebeurt. Voor elke regel van het invoerbestand:

fs.createReadStream(myArgs[1]) .pipe(csv.parse({ headers: false })) .on("data", async (email) => { let url = SPARKPOST_HOST + "/api/v1/recipient-validation/single/" + email; await axios .get(url, { headers: { Authorization: SPARKPOST_API_KEY, }, }) //Voor elke rij die uit het invoerbestand wordt gelezen, roep de SparkPost Recipient Validation API aan


Vervolgens, bij de respons

  • Voeg de e-mail toe aan de JSON (om de e-mail in de CSV te kunnen printen)

  • Valideer of de reden nul is, en zo ja, vul een lege waarde in (dit is zodat het CSV-formaat consistent is, aangezien in sommige gevallen een reden wordt gegeven in de respons)

  • Stel de opties en sleutels in voor de json2csv-module.

  • Converteer de JSON naar CSV en voer uit (gebruik makend van json2csv)

  • Schrijf de voortgang in de terminal

  • Tenslotte, als het aantal e-mails in het bestand = voltooide validaties, stop de timer en print de resultaten uit


.then(function (response) { response.data.results.email = String(email); //Voegt de e-mail toe als een waarde/sleutelpaar aan de respons-JSON voor gebruik in de uitvoer response.data.results.reason ? null : (response.data.results.reason = ""); //Als de reden nul is, zet deze op leeg zodat de CSV uniform is //Maakt gebruik van json-2-csv om de JSON naar CSV-formaat te converteren en uitvoeren let options = { prependHeader: false, //Schakelt JSON-waarden uit om als koprijen voor elke regel toe te voegen keys: [ "results.email", "results.valid", "results.result", "results.reason", "results.is_role", "results.is_disposable", "results.is_free", "results.delivery_confidence", ], //Stelt de volgorde van sleutels in }; let json2csvCallback = function (err, csv) { if (err) throw err; output.write(`${csv}\n`); }; converter.json2csv(response.data, json2csvCallback, options); completed++; //Verhoog de API-teller process.stdout.write(`Klaar met ${completed} / ${email_count}\r`); //Output status van Voltooid / Totaal naar de console zonder nieuwe regels te tonen //Als alle e-mails zijn gevalideerd if (completed == email_count) { const stop = window.performance.now(); //Stop de timer console.log( `Alle e-mails met succes gevalideerd in ${ (stop - start) / 1000 } seconden` ); } })

 

Een laatste probleem dat ik vond was hoewel dit goed werkte op Mac, ik de volgende fout ondervond op 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 verbinding 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 optreedt en niet op Mac.

Volgende Stappen

Voor iemand die op zoek is naar een eenvoudig snel programma dat een CSV bestand inlaadt, de ontvanger validatie API aanroept, en een CSV output, is dit programma voor jou.

Enkele uitbreidingen aan dit programma zouden het volgende zijn:

  • Bouw een front-end of makkelijker UI voor gebruik

  • Betere fout- en retry-afhandeling, want als de API om de een of andere reden een fout geeft, herhaalt het programma momenteel de oproep niet


Ik ben ook benieuwd of snellere resultaten bereikt kunnen worden met een andere taal zoals Golang of Erlang/Elixir.

Voel je vrij om me feedback of suggesties te geven voor het uitbreiden van dit project.

Meld je aan voor onze nieuwsbrief.

Blijf op de hoogte met Bird via wekelijkse updates in je inbox.

Door te verzenden, ga je ermee akkoord dat Bird contact met je mag opnemen over onze producten en diensten.

U kunt zich op elk moment afmelden. Zie Bird's Privacyverklaring voor details over gegevensverwerking.

Meld je aan voor onze nieuwsbrief.

Blijf op de hoogte met Bird via wekelijkse updates in je inbox.

Door te verzenden, ga je ermee akkoord dat Bird contact met je mag opnemen over onze producten en diensten.

U kunt zich op elk moment afmelden. Zie Bird's Privacyverklaring voor details over gegevensverwerking.

Meld je aan voor onze nieuwsbrief.

Blijf op de hoogte met Bird via wekelijkse updates in je inbox.

Door te verzenden, ga je ermee akkoord dat Bird contact met je mag opnemen over onze producten en diensten.

U kunt zich op elk moment afmelden. Zie Bird's Privacyverklaring voor details over gegevensverwerking.

Pinterest-logo
Uber-logo
Square logo
Adobe-logo
Meta-logo
PayPal-logo

Bedrijf

Nieuwsbrief

Blijf op de hoogte met Bird via wekelijkse updates in je inbox.

Door te verzenden, ga je ermee akkoord dat Bird contact met je mag opnemen over onze producten en diensten.

U kunt zich op elk moment afmelden. Zie Bird's Privacyverklaring voor details over gegevensverwerking.

Uber-logo
Square logo
Adobe-logo
Meta-logo

Bedrijf

Nieuwsbrief

Blijf op de hoogte met Bird via wekelijkse updates in je inbox.

Door te verzenden, ga je ermee akkoord dat Bird contact met je mag opnemen over onze producten en diensten.

U kunt zich op elk moment afmelden. Zie Bird's Privacyverklaring voor details over gegevensverwerking.

Uber-logo
Adobe-logo
Meta-logo

Bereik

Grow

Manage

Automate

Bronnen

Bedrijf

Nieuwsbrief

Blijf op de hoogte met Bird via wekelijkse updates in je inbox.

Door te verzenden, ga je ermee akkoord dat Bird contact met je mag opnemen over onze producten en diensten.

U kunt zich op elk moment afmelden. Zie Bird's Privacyverklaring voor details over gegevensverwerking.