Reach

Grow

Manage

Automate

Reach

Grow

Manage

Automate

Membangun Alat Validasi Penerima Burung Asinkron Massal

Zachary Samuels

26 Mei 2022

Email

1 min read

Membangun Alat Validasi Penerima Burung Asinkron Massal

Zachary Samuels

26 Mei 2022

Email

1 min read

Membangun Alat Validasi Penerima Burung Asinkron Massal

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

Saat membangun aplikasi email, pengembang sering kali perlu mengintegrasikan beberapa layanan dan API. Memahami fundamental API email dalam infrastruktur cloud memberikan dasar untuk membangun alat yang kuat seperti sistem validasi massal yang akan kita buat dalam panduan ini.

Salah satu pertanyaan yang kadang-kadang kami terima adalah, bagaimana saya dapat memvalidasi daftar email secara massal dengan validasi penerima? Ada dua opsi di sini, yang pertama adalah mengunggah file melalui UI SparkPost untuk validasi, dan yang lainnya adalah membuat panggilan individual per email ke API (karena API adalah validasi email tunggal).

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

Karena mengunggah ribuan file CSV tampaknya agak aneh, saya mengambil kasus penggunaan itu dan mulai bertanya-tanya seberapa cepat saya bisa membuat API berjalan. Dalam posting blog ini, saya akan menjelaskan apa yang saya coba dan bagaimana saya akhirnya mencapai program yang dapat menjalankan sekitar 100.000 validasi dalam 55 detik (Sedangkan dalam UI saya mendapatkan sekitar 100.000 validasi dalam 1 menit 10 detik). Dan sementara ini masih akan memakan waktu sekitar 100 jam untuk selesai dengan sekitar 654 juta validasi, skrip ini dapat berjalan di latar belakang menghemat waktu yang signifikan.

Versi akhir dari program ini dapat ditemukan di sini.

Kesalahan pertama saya: menggunakan Python

Python adalah salah satu bahasa pemrograman favorit saya. Ini unggul di banyak area dan sangat mudah digunakan. Namun, satu area di mana Python tidak unggul adalah proses concurrent. Meskipun Python memiliki kemampuan untuk menjalankan fungsi asinkron, ia memiliki apa yang dikenal sebagai Python Global Interpreter Lock atau GIL.

“Python Global Interpreter Lock atau GIL, dalam kata-kata sederhana, adalah mutex (atau kunci) yang hanya memungkinkan satu thread untuk mengendalikan interpreter Python.

Ini berarti bahwa hanya satu thread yang dapat berada dalam keadaan eksekusi pada satu waktu. Dampak GIL tidak terlihat oleh pengembang yang menjalankan program single-threaded, tetapi dapat menjadi hambatan performa dalam kode yang mengandalkan CPU dan multi-threaded.

Karena GIL hanya memungkinkan satu thread untuk dieksekusi pada satu waktu bahkan dalam arsitektur multi-threaded dengan lebih dari satu inti CPU, GIL telah mendapatkan reputasi sebagai fitur "infamous" dari Python.” (https://realpython.com/python-gil/)”

Awalnya, saya tidak menyadari adanya GIL, jadi saya mulai memprogram dengan Python. Pada akhirnya, meskipun program saya bersifat asinkron, tetap terjebak, dan tidak peduli berapa banyak thread yang saya tambahkan, saya tetap 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 membuang penggunaan Python dan kembali ke papan gambar…

Saya memutuskan untuk menggunakan NodeJS karena kemampuannya dalam menjalankan operasi i/o non-blokir dengan sangat baik. Opsi lain yang cukup baik untuk menangani pemrosesan API asinkron adalah membangun consumers webhook serverless dengan Azure Functions, yang dapat menangani beban kerja variabel dengan efisien. Saya juga cukup familiar dengan pemrograman di NodeJS.

Dengan memanfaatkan aspek asinkron dari NodeJS, ini akhirnya bekerja dengan baik. Untuk lebih detail tentang pemrograman asinkron di NodeJS, lihat 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:

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.


Pertama, masukkan daftar email CSV. Kedua, muat email ke dalam array dan pastikan bahwa mereka dalam format yang benar. Ketiga, panggil API validasi penerima secara asinkron. Keempat, tunggu hasilnya dan muat ke dalam variabel. Dan akhirnya, keluarkan variabel ini ke file CSV.

Ini bekerja dengan sangat baik untuk file yang lebih kecil. Masalahnya muncul ketika saya mencoba menjalankan 100.000 email. Program terhenti sekitar 12.000 validasi. Dengan bantuan salah satu pengembang front-end kami, saya melihat bahwa masalahnya adalah dengan memuat semua hasil ke dalam variabel (dan karena itu kehabisan memori dengan cepat). Jika Anda ingin melihat iterasi pertama program ini, saya telah menautkannya di sini: 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.


Pertama, masukkan daftar email CSV. Kedua, hitung jumlah email dalam file untuk tujuan pelaporan. Ketiga, saat 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 pengecekan sintaks email setelah berbicara dengan tim validasi penerima, karena mereka memberi tahu saya bahwa validasi penerima sudah memiliki pengecekan bawaan untuk memeriksa apakah email valid atau tidak.

Ide awal saya adalah sebagai berikut:

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.


Pertama, masukkan daftar email CSV. Kedua, muat email ke dalam array dan pastikan bahwa mereka dalam format yang benar. Ketiga, panggil API validasi penerima secara asinkron. Keempat, tunggu hasilnya dan muat ke dalam variabel. Dan akhirnya, keluarkan variabel ini ke file CSV.

Ini bekerja dengan sangat baik untuk file yang lebih kecil. Masalahnya muncul ketika saya mencoba menjalankan 100.000 email. Program terhenti sekitar 12.000 validasi. Dengan bantuan salah satu pengembang front-end kami, saya melihat bahwa masalahnya adalah dengan memuat semua hasil ke dalam variabel (dan karena itu kehabisan memori dengan cepat). Jika Anda ingin melihat iterasi pertama program ini, saya telah menautkannya di sini: 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.


Pertama, masukkan daftar email CSV. Kedua, hitung jumlah email dalam file untuk tujuan pelaporan. Ketiga, saat 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 pengecekan sintaks email setelah berbicara dengan tim validasi penerima, karena mereka memberi tahu saya bahwa validasi penerima sudah memiliki pengecekan bawaan untuk memeriksa apakah email valid atau tidak.

Ide awal saya adalah sebagai berikut:

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.


Pertama, masukkan daftar email CSV. Kedua, muat email ke dalam array dan pastikan bahwa mereka dalam format yang benar. Ketiga, panggil API validasi penerima secara asinkron. Keempat, tunggu hasilnya dan muat ke dalam variabel. Dan akhirnya, keluarkan variabel ini ke file CSV.

Ini bekerja dengan sangat baik untuk file yang lebih kecil. Masalahnya muncul ketika saya mencoba menjalankan 100.000 email. Program terhenti sekitar 12.000 validasi. Dengan bantuan salah satu pengembang front-end kami, saya melihat bahwa masalahnya adalah dengan memuat semua hasil ke dalam variabel (dan karena itu kehabisan memori dengan cepat). Jika Anda ingin melihat iterasi pertama program ini, saya telah menautkannya di sini: 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.


Pertama, masukkan daftar email CSV. Kedua, hitung jumlah email dalam file untuk tujuan pelaporan. Ketiga, saat 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 pengecekan sintaks email setelah berbicara dengan tim validasi penerima, karena mereka memberi tahu saya bahwa validasi penerima sudah memiliki pengecekan bawaan untuk memeriksa apakah email valid atau tidak.

Memecah kode terakhir

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) ini memungkinkan saya untuk secara akurat melaporkan kemajuan file [seperti yang akan kita lihat nanti], dan 2) ini memungkinkan saya untuk menghentikan timer ketika jumlah email dalam file sama dengan validasi yang telah selesai. Saya menambahkan timer agar saya bisa menjalankan tolok ukur dan memastikan saya mendapatkan hasil yang baik.

let count = 0; //Penghitungan baris require("fs") .createReadStream(myArgs[1]) .on("data", function (chunk) { for (let i = 0; i < chunk.length; ++i) if (chunk[i] == 10) count++; }) //Membaca infile dan menambah jumlah untuk setiap baris .on("close", function () { //Pada akhir infile, setelah semua baris dihitung, jalankan fungsi validasi penerima validateRecipients.validateRecipients(count, myArgs); });


 Kemudian saya memanggil fungsi validateRecipients. Catat bahwa fungsi ini asinkron. Setelah memvalidasi bahwa infile dan outfile adalah CSV, saya menulis baris header, dan memulai timer program menggunakan perpustakaan JSDOM.

async function validateRecipients(email_count, myArgs) { if ( //Jika baik infile dan outfile dalam format .csv extname(myArgs[1]).toLowerCase() == ".csv" && extname(myArgs[3]).toLowerCase() == ".csv" ) { let completed = 0; //Penghitung untuk setiap panggilan API email_count++; //Penghitung baris mengembalikan #baris - 1, ini dilakukan untuk memperbaiki jumlah baris //Memulai 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" ); //Menulis header dalam outfile


Skrip berikut ini adalah inti dari program sehingga saya akan memecahnya 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, }, }) //Untuk setiap baris yang dibaca dari infile, memanggil SparkPost Recipient Validation API


Kemudian, pada responsnya:

  • Tambahkan email ke JSON (agar dapat mencetak email dalam CSV)

  • Validasi jika alasan (reason) adalah null, dan jika iya, isi dengan nilai kosong (ini agar format CSV konsisten, karena dalam beberapa kasus alasan diberikan dalam respons)

  • Tentukan opsi dan kunci untuk modul json2csv.

  • Konversi JSON ke CSV dan keluaran (menggunakan json2csv)

  • Tulis kemajuan di terminal

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


.then(function (response) { response.data.results.email = String(email); //Menambahkan email sebagai nilai/pasangan kunci ke JSON respons untuk digunakan dalam keluaran response.data.results.reason ? null : (response.data.results.reason = ""); //Jika alasan adalah null, atur ke kosong sehingga CSV uniform //Menggunakan json-2-csv untuk mengubah JSON menjadi format CSV dan keluaran let options = { prependHeader: false, //Menonaktifkan nilai JSON ditambah sebagai baris header setiap baris keys: [ "results.email", "results.valid", "results.result", "results.reason", "results.is_role", "results.is_disposable", "results.is_free", "results.delivery_confidence", ], //Mengatur urutan kunci }; let json2csvCallback = function (err, csv) { if (err) throw err; output.write(`${csv}\n`); }; converter.json2csv(response.data, json2csvCallback, options); completed++; //Meningkatkan penghitung API process.stdout.write(`Selesai dengan ${completed} / ${email_count}\r`); //Mengeluarkan status Selesai / Total ke konsol tanpa menampilkan baris baru //Jika semua email telah selesai divalidasi if (completed == email_count) { const stop = window.performance.now(); //Menghentikan timer console.log( `Semua email berhasil divalidasi dalam ${ (stop - start) / 1000 } detik` ); } })

 

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

Error: connect ENOBUFS XX.XX.XXX.XXX:443 – Lokal (undefined:undefined) dengan email XXXXXXX@XXXXXXXXXX.XXX

Setelah melakukan beberapa penelitian lebih lanjut, tampaknya ini adalah masalah dengan pool koneksi klien HTTP NodeJS yang tidak memanfaatkan koneksi yang sudah ada. Saya menemukan artikel Stackoverflow tentang masalah ini, dan setelah menggali lebih dalam, menemukan konfigurasi default yang bagus untuk pustaka axios yang menyelesaikan masalah ini. Saya masih belum yakin mengapa isu ini hanya terjadi di Windows dan tidak di Mac.

Langkah Selanjutnya

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

Beberapa penambahan pada program ini diantaranya:

  • Membangun antarmuka depan atau UI yang lebih mudah digunakan

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

  • Pertimbangkan untuk mengimplementasikan sebagai serverless Azure Function untuk penskalaan otomatis dan pengurangan manajemen infrastruktur


Saya juga ingin tahu apakah hasil yang lebih cepat dapat dicapai dengan bahasa lain seperti Golang atau Erlang/Elixir. Di luar pemilihan bahasa, batasan infrastruktur juga dapat memengaruhi kinerja - kami belajar ini secara langsung ketika kami menemui batas DNS yang tidak terdokumentasi di AWS yang memengaruhi sistem pemrosesan email volume tinggi kami.

Untuk pengembang yang tertarik menggabungkan pemrosesan API dengan alat alur kerja visual, lihat cara mengintegrasikan Flow Builder dengan Google Cloud Functions untuk alur kerja otomasi tanpa kode.

Silakan berikan saya umpan balik atau saran untuk mengembangkan proyek ini.

Mari hubungkan Anda dengan pakar Bird.
Lihat kekuatan penuh dari Bird dalam 30 menit.

Dengan mengirimkan, Anda setuju Bird dapat menghubungi Anda tentang produk dan layanan kami.

Anda dapat berhenti berlangganan kapan saja. Lihat Pernyataan Privasi Bird untuk detail tentang pemrosesan data.

Perusahaan

Newsletter

Tetap terinformasi dengan Bird melalui pembaruan mingguan ke kotak masuk Anda.

Mari hubungkan Anda dengan pakar Bird.
Lihat kekuatan penuh dari Bird dalam 30 menit.

Dengan mengirimkan, Anda setuju Bird dapat menghubungi Anda tentang produk dan layanan kami.

Anda dapat berhenti berlangganan kapan saja. Lihat Pernyataan Privasi Bird untuk detail tentang pemrosesan data.

Perusahaan

Newsletter

Tetap terinformasi dengan Bird melalui pembaruan mingguan ke kotak masuk Anda.

Mari hubungkan Anda dengan pakar Bird.
Lihat kekuatan penuh dari Bird dalam 30 menit.

Dengan mengirimkan, Anda setuju Bird dapat menghubungi Anda tentang produk dan layanan kami.

Anda dapat berhenti berlangganan kapan saja. Lihat Pernyataan Privasi Bird untuk detail tentang pemrosesan data.

R

Reach

G

Grow

M

Manage

A

Automate

Perusahaan

Newsletter

Tetap terinformasi dengan Bird melalui pembaruan mingguan ke kotak masuk Anda.