Membangun Alat Validasi Penerima Bird Asinkron Massal

Membangun Alat Validasi Penerima Bird Asinkron Massal

Membangun Alat Validasi Penerima Bird Asinkron Massal

May 26, 2022

Diterbitkan oleh

Diterbitkan oleh

Zachary Samuels

Zachary Samuels

Kategori:

Kategori:

Email

Email

Ready to see Bird
in action?

Ready to see Bird
in action?

Building a Bulk Asynchronous Bird Recipient Validation Tool

One of the questions we occasionally receive is, how can I bulk validate email lists with validasi penerima? There are two options here, one is to upload a file through the SparkPost UI for validation, and the other is to make individual calls per email ke API (as the API is single email validation).


Opsi pertama berfungsi dengan baik tetapi memiliki batasan 20Mb (sekitar 500.000 alamat). Bagaimana jika seseorang memiliki daftar email yang berisi jutaan alamat? Ini bisa berarti membaginya menjadi 1.000 unggahan file CSV.


Since uploading thousands of CSV files seems a little far-fetched, I took that use case and began to wonder how fast I could get the API to run. In this blog post, I will explain what I tried and how I eventually came to a program that could get around 100.000 validasi dalam 55 detik (Whereas in the UI I got around 100,000 validations in 1 minute 10 seconds). And while this still would take about 100 hours to get done with about 654 million validations, this script can run in the background saving significant time.


Versi terakhir dari program ini dapat ditemukan here.


Kesalahan pertama saya: menggunakan Python

Python adalah salah satu bahasa pemrograman favorit saya. Bahasa ini unggul di banyak bidang dan sangat mudah. Namun, satu bidang yang tidak unggul adalah proses bersamaan. Meskipun python memiliki kemampuan untuk menjalankan fungsi-fungsi asinkron, namun ia memiliki apa yang dikenal sebagai Kunci Penerjemah Global Python atau GIL.


"Kunci Interpreter Global Python atau GIL, dengan kata sederhana, adalah mutex (atau kunci) yang memungkinkan hanya satu utas yang memegang kendali atas interpreter Python.


Ini berarti bahwa hanya satu thread yang dapat berada dalam kondisi eksekusi pada suatu waktu. Dampak GIL tidak terlihat oleh pengembang yang menjalankan program berulir tunggal, tetapi dapat menjadi hambatan kinerja dalam kode yang terikat CPU dan multi-ulir.


Since the GIL allows only one thread to execute at a time even in a multi-threaded architecture with more than one CPU core, the GIL has gained a reputation as an “infamous” feature of Python.” (https://realpython.com/python-gil/)”


Pada awalnya, saya tidak mengetahui tentang GIL, jadi saya mulai memprogram dalam bahasa pemrograman python. Pada akhirnya, meskipun program saya asinkron, program saya terkunci, dan tidak peduli berapa banyak utas yang saya tambahkan, saya masih hanya mendapatkan sekitar 12-15 iterasi per detik.


Bagian utama dari fungsi asinkron dalam Python dapat dilihat di bawah ini:

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)

 

Jadi saya membatalkan penggunaan Python dan kembali ke papan gambar...


Saya memilih untuk menggunakan NodeJS karena kemampuannya untuk melakukan operasi i/o non-blocking dengan sangat baik. Saya juga cukup akrab dengan pemrograman di NodeJS.


Utilizing asynchronous aspects of NodeJS, this ended up working well. For more details about asynchronous programming in NodeJS, see https://blog.risingstack.com/node-hero-async-programming-in-node-js/


Kesalahan kedua saya: mencoba membaca file ke dalam memori

Ide awal saya adalah sebagai berikut:



Pertama, masukkan daftar email CSV. Kedua, muat email ke dalam larik dan periksa apakah email tersebut dalam format yang benar. Ketiga, panggil API validasi penerima secara asinkron. Keempat, tunggu hasilnya dan muat ke dalam sebuah variabel. Dan terakhir, keluarkan variabel ini ke file CSV.


This worked very well for smaller files. The issue became when I tried to run 100,000 emails through. The program stalled at around 12,000 validations. With the help of one of our front-end developers, I saw that the issue was with loading all the results into a variable (and therefore running out of memory quickly). If you would like to see the first iteration of this program, I have linked it here: Versi 1 (TIDAK DIREKOMENDASIKAN).



Pertama, masukkan daftar email CSV. Kedua, hitung jumlah email dalam file untuk tujuan pelaporan. Ketiga, karena setiap baris dibaca secara asinkron, panggil API validasi penerima dan keluarkan hasilnya ke file CSV.


Jadi, untuk setiap baris yang dibaca, saya memanggil API dan menuliskan hasilnya secara asinkron sehingga tidak menyimpan data ini dalam memori jangka panjang. Saya juga menghapus pemeriksaan sintaksis email setelah berbicara dengan tim validasi penerima, karena mereka memberi tahu saya bahwa validasi penerima sudah memiliki pemeriksaan bawaan untuk memeriksa apakah sebuah email valid atau tidak.


Memecah kode akhir

Setelah membaca dan memvalidasi argumen terminal, saya menjalankan kode berikut. Pertama, saya membaca file CSV email dan menghitung setiap baris. Ada dua tujuan dari fungsi ini, 1) memungkinkan saya untuk melaporkan kemajuan file secara akurat [seperti yang akan kita lihat nanti], dan 2) memungkinkan saya untuk menghentikan pengatur waktu ketika jumlah email dalam file sama dengan validasi yang telah selesai. Saya menambahkan pengatur waktu agar saya bisa menjalankan tolok ukur dan memastikan bahwa saya mendapatkan hasil yang baik.


let count = 0; //Line count require("fs") .createReadStream(myArgs[1]) .on("data", function (chunk) { for (let i = 0; i < chunk.length; ++i) if (chunk[i] == 10) count++; }) //Reads the infile and increases the count for each line .on("close", function () { //At the end of the infile, after all lines have been counted, run the recipient validation function validateRecipients.validateRecipients(count, myArgs); });

 

Saya kemudian memanggil fungsi validateRecipients. Perhatikan bahwa fungsi ini bersifat asinkron. Setelah memvalidasi bahwa infile dan outfile adalah CSV, saya menulis baris header, dan memulai pengatur waktu program menggunakan pustaka JSDOM.


async function validateRecipients(email_count, myArgs) { if ( //If both the infile and outfile are in .csv format extname(myArgs[1]).toLowerCase() == ".csv" && extname(myArgs[3]).toLowerCase() == ".csv" ) { let completed = 0; //Counter for each API call email_count++; //Line counter returns #lines - 1, this is done to correct the number of lines //Start a timer const { window } = new JSDOM(); const start = window.performance.now(); const output = fs.createWriteStream(myArgs[3]); //Outfile output.write( "Email,Valid,Result,Reason,Is_Role,Is_Disposable,Is_Free,Delivery_Confidence\n" ); //Write the headers in the outfile

 

Skrip berikut ini adalah inti dari program ini, jadi saya akan menguraikannya dan menjelaskan apa yang terjadi. Untuk setiap baris dari infile:


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, }, }) //For each row read in from the infile, call the SparkPost Recipient Validation API

 

Kemudian, pada tanggapan

  • Tambahkan email ke JSON (untuk dapat mencetak email dalam bentuk CSV)

  • Validasi jika alasannya nol, dan jika demikian, isi nilai kosong (ini agar format CSV konsisten, karena dalam beberapa kasus alasan diberikan dalam respons)

  • Mengatur opsi dan kunci untuk modul json2csv.

  • Mengonversi JSON ke CSV dan keluaran (memanfaatkan json2csv)

  • Menulis kemajuan di terminal

  • Terakhir, jika jumlah email dalam file = validasi selesai, hentikan timer dan cetak hasilnya


.then(function (response) { response.data.results.email = String(email); //Adds the email as a value/key pair ke response JSON to be used for output response.data.results.reason ? null : (response.data.results.reason = ""); //If reason is null, set it to blank so the CSV is uniform //Utilizes json-2-csv to convert the JSON to CSV format and output let options = { prependHeader: false, //Disables JSON values from being added as header rows for every line keys: [ "results.email", "results.valid", "results.result", "results.reason", "results.is_role", "results.is_disposable", "results.is_free", "results.delivery_confidence", ], //Sets the order of keys }; let json2csvCallback = function (err, csv) { if (err) throw err; output.write(`${csv}\n`); }; converter.json2csv(response.data, json2csvCallback, options); completed++; //Increase the API counter process.stdout.write(`Done with ${completed} / ${email_count}\r`); //Output status of Completed / Total to the console without showing new lines //If all emails have completed validation if (completed == email_count) { const stop = window.performance.now(); //Stop the timer console.log( `All emails successfully validated in ${ (stop - start) / 1000 } seconds` ); } })

 

Satu masalah terakhir yang saya temukan adalah meskipun ini bekerja dengan baik di Mac, saya mengalami kesalahan berikut ini menggunakan Windows setelah sekitar 10.000 validasi:


Kesalahan: hubungkan ENOBUFS XX.XX.XXX.XXX:443 - Lokal (tidak terdefinisi: tidak terdefinisi) dengan email XXXXXXX@XXXXXXXXXX.XXX


After doing some further research, it appears to be an issue with the NodeJS HTTP client connection pool not reusing connections. I found this Artikel Stackoverflow on the issue, and after further digging, found a good konfigurasi default for the axios library that resolved this issue. I am still not certain why this issue only happens on Windows and not on Mac.


Langkah Selanjutnya

Untuk seseorang yang mencari program cepat sederhana yang menerima csv, memanggil API validasi penerima, dan mengeluarkan CSV, program ini cocok untuk Anda.


Beberapa tambahan untuk program ini adalah sebagai berikut:

  • Membangun front end atau UI yang lebih mudah untuk digunakan

  • Penanganan kesalahan dan percobaan ulang yang lebih baik karena jika karena alasan tertentu API melemparkan kesalahan, program saat ini tidak mencoba ulang panggilan


Saya juga ingin tahu apakah hasil yang lebih cepat dapat dicapai dengan bahasa lain seperti Golang atau Erlang/Elixir.


Please feel free to provide me any umpan balik atau saran for expanding this project.

Your new standard in Marketing, Pay & Sales. It's Bird

The right message -> to the right person -> di right time.

By clicking "See Bird" you agree to Bird's Pemberitahuan Privasi.

Your new standard in Marketing, Pay & Sales. It's Bird

The right message -> to the right person -> di right time.

By clicking "See Bird" you agree to Bird's Pemberitahuan Privasi.