
Per chi cerca un programma semplice e veloce che prenda un CSV, chiami l'API di convalida del destinatario e produca un CSV, questo programma fa per te.
Quando si costruiscono applicazioni email, gli sviluppatori spesso devono integrare più servizi e API. Comprendere i fondamenti dell'email API nell'infrastruttura cloud fornisce la base per costruire strumenti robusti come il sistema di validazione in massa che creeremo in questa guida.
Una delle domande che riceviamo occasionalmente è, come posso validare in massa liste di email con la validazione dei destinatari? Ci sono due opzioni qui, una è caricare un file tramite l'interfaccia utente SparkPost per la validazione, e l'altra è effettuare chiamate individuali per email all'API (dato che l'API è per la validazione di singole email).
La prima opzione funziona alla grande ma ha un limite di 20 Mb (circa 500.000 indirizzi). E se qualcuno avesse una lista di email contenente milioni di indirizzi? Potrebbe significare suddividere il tutto in migliaia di caricamenti di file CSV.
Poiché caricare migliaia di file CSV sembra un po' inverosimile, ho preso questo caso d'uso e ho cominciato a chiedermi quanto velocemente potessi far funzionare l'API. In questo post del blog, spiegherò cosa ho provato e come sono arrivato infine a un programma che potrebbe ottenere circa 100.000 validazioni in 55 secondi (mentre nell'interfaccia utente ho ottenuto circa 100.000 validazioni in 1 minuto e 10 secondi). E mentre questo richiederebbe ancora circa 100 ore per completare circa 654 milioni di validazioni, questo script può essere eseguito in background risparmiando tempo considerevole.
La versione finale di questo programma può essere trovata qui.
Il mio primo errore: usare Python
Python è uno dei miei linguaggi di programmazione preferiti. Eccelle in molte aree ed è incredibilmente semplice. Tuttavia, un'area in cui non eccelle è nei processi concorrenti. Sebbene Python abbia la capacità di eseguire funzioni asincrone, ha ciò che è noto come The Python Global Interpreter Lock o GIL.
“The Python Global Interpreter Lock o GIL, in parole semplici, è un mutex (o un blocco) che consente a un solo thread di tenere il controllo dell'interprete Python.
Ciò significa che solo un thread può essere in uno stato di esecuzione in qualsiasi momento. L'impatto del GIL non è visibile agli sviluppatori che eseguono programmi single-threaded, ma può essere un collo di bottiglia delle prestazioni nel codice vincolato dalla CPU e multi-thread.
Poiché il Global Interpreter Lock (GIL) consente a un solo thread di essere eseguito alla volta, anche su sistemi multi-core, ha guadagnato una reputazione come una caratteristica “infame” di Python (vedi l'articolo di Real Python sul GIL).
All'inizio non ero a conoscenza del GIL, quindi ho iniziato a programmare in Python. Alla fine, anche se il mio programma era asincrono, si bloccava, e indipendentemente da quanti thread aggiungevo, ottenevo comunque solo circa 12-15 iterazioni al secondo.
La parte principale della funzione asincrona in Python può essere vista qui sotto:
Così ho abbandonato l'uso di Python e sono tornato al tavolo da disegno...
Ho deciso di utilizzare NodeJS per la sua capacità di eseguire operazioni di I/O non bloccanti estremamente bene. Un'altra eccellente opzione per gestire l'elaborazione asincrona delle API è la costruzione di consumatori di webhook serverless con Azure Functions, che possono gestire efficacemente carichi di lavoro variabili. Inoltre, sono piuttosto familiare con la programmazione in NodeJS.
Utilizzando aspetti asincroni di Node.js, questo approccio ha funzionato bene. Per ulteriori dettagli sulla programmazione asincrona in Node.js, vedi la guida di RisingStack alla programmazione asincrona in Node.js.
Il mio secondo errore: cercare di leggere il file nella memoria
Scomposizione del codice finale
Dopo aver letto e validato gli argomenti del terminale, eseguo il seguente codice. Innanzitutto, leggo il file CSV delle email e conto ogni riga. Ci sono due scopi per questa funzione: 1) mi permette di riportare accuratamente il progresso del file [come vedremo più avanti], e 2) mi permette di fermare un timer quando il numero di email nel file è uguale alle validazioni completate. Ho aggiunto un timer in modo da poter eseguire benchmark e garantire buoni risultati.
Chiamo quindi la funzione validateRecipients. Nota che questa funzione è asincrona. Dopo aver validato che infile e outfile siano CSV, scrivo una riga di intestazione e avvio un timer del programma utilizzando la libreria JSDOM.
Lo script seguente è davvero la parte principale del programma, quindi lo dividerò e spiegherò cosa sta accadendo. Per ogni riga di infile:
Prendere asincronamente quella riga e chiamare l' recipient validation API.
Quindi, alla risposta
Aggiungere l'email al JSON (per poter stampare l'email nel CSV)
Validare se il motivo è nullo e, in tal caso, popolare un valore vuoto (questo è per mantenere il formato CSV coerente, poiché in alcuni casi il motivo è fornito nella risposta)
Impostare le opzioni e le chiavi per il modulo json2csv.
Convertire il JSON in CSV e eseguire l'output (utilizzando json2csv)
Scrivi il progresso nel terminale
Infine, se il numero di email nel file = validazioni completate, fermare il timer e stampare i risultati
Un ultimo problema che ho trovato è che mentre questo funzionava bene su Mac, ho riscontrato il seguente errore utilizzando Windows dopo circa 10.000 validazioni:
Errore: connect ENOBUFS XX.XX.XXX.XXX:443 – Local (undefined:undefined) con email XXXXXXX@XXXXXXXXXX.XXX
Dopo aver effettuato ulteriori ricerche, sembra essere un problema con il pool di connessioni del client HTTP NodeJS che non riutilizza le connessioni. Ho trovato questo Stackoverflow article sul problema e, dopo ulteriori scavi, ho trovato una buona configurazione di default per la libreria axios che ha risolto questo problema. Non sono ancora certo del motivo per cui questo problema si verifica solo su Windows e non su Mac.
Passi successivi
Per chi cerca un programma semplice e veloce che accetta un CSV, chiama l'API di validazione del destinatario e produce un CSV, questo programma fa per te.
Alcune aggiunte a questo programma potrebbero essere le seguenti:
Costruire un front-end o un'interfaccia utente più semplice da usare
Migliorare la gestione degli errori e dei tentativi perché se per qualche motivo l'API genera un errore, attualmente il programma non ritenta la chiamata
Considera l'implementazione come una funzione serverless di Azure per scalabilità automatica e gestione ridotta dell'infrastruttura
Sarei anche curioso di vedere se risultati più rapidi potrebbero essere ottenuti con un altro linguaggio come Golang o Erlang/Elixir. Oltre alla scelta del linguaggio, le limitazioni dell'infrastruttura possono anche influire sulle prestazioni - lo abbiamo appreso in prima persona quando abbiamo incontrato i limiti DNS non documentati in AWS che hanno influenzato i nostri sistemi di elaborazione email ad alto volume.
Per gli sviluppatori interessati a combinare l'elaborazione delle API con strumenti di flusso di lavoro visivi, scopri come integrare Flow Builder con Google Cloud Functions per flussi di lavoro di automazione senza codice.
Non esitate a fornirmi qualsiasi feedback o suggerimento per ampliare questo progetto.