Bereik

Grow

Manage

Automate

Bereik

Grow

Manage

Automate

Een Bulk Asynchroon Bird Ontvanger Validatie Tool Bouwen

Zachary Samuels

26 mei 2022

E-mail

1 min read

Een Bulk Asynchroon Bird Ontvanger Validatie Tool Bouwen

Zachary Samuels

26 mei 2022

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.

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. Eén gebied waarin het echter niet excelleert, zijn gelijktijdige processen. Hoewel Python de mogelijkheid heeft om asynchrone functies uit te voeren, heeft het wat 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) die slechts één thread toestaat de controle over de Python-interpreter te houden.

Dit betekent dat slechts één thread op elk moment in een uitvoeringsstaat kan zijn. De impact van de GIL is niet zichtbaar voor ontwikkelaars die enkelvoudige, eendraadse programma's uitvoeren, maar het kan een prestatieknelpunt zijn in CPU-intensieve en meerdradige code.

Aangezien de GIL slechts één thread toestaat om tegelijkertijd uit te voeren, zelfs in een meerdradige architectuur met meer dan één CPU-kern, heeft de GIL de reputatie van 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 in Python te programmeren. Aan het eind, hoewel mijn programma asynchroon was, sloot het zich op, 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 stopte ik met het gebruik van Python en ging terug naar de tekentafel…

Ik besloot NodeJS te gebruiken vanwege zijn vermogen om niet-blokkerende i/o-bewerkingen extreem goed uit te voeren. Een andere uitstekende optie voor het verwerken van asynchrone API-verwerking is het bouwen van serverloze webhook-consumenten met Azure Functions, die efficiënt variabele workloads kan verwerken. Ik ben ook vrij bekend met programmeren in NodeJS.

Door gebruik te maken van de asynchrone aspecten van NodeJS, werkte dit goed. Voor meer details over asynchrone programmering 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 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.

Laten we je in contact brengen met een Bird-expert.
Bekijk de volledige kracht van de Bird in 30 minuten.

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.

Nieuwsbrief

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

Laten we je in contact brengen met een Bird-expert.
Bekijk de volledige kracht van de Bird in 30 minuten.

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.

Nieuwsbrief

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

Laten we je in contact brengen met een Bird-expert.
Bekijk de volledige kracht van de Bird in 30 minuten.

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.

R

Bereik

G

Grow

M

Manage

A

Automate

Nieuwsbrief

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