Product

Solusi

Sumber Daya

Company

Product

Solusi

Sumber Daya

Company

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. Bahasa ini unggul di banyak bidang dan sangat mudah dipahami. Namun, salah satu area di mana ia tidak unggul adalah pada proses konkuren. Meskipun python memiliki kemampuan untuk menjalankan fungsi asinkron, ia memiliki apa yang dikenal sebagai The Python Global Interpreter Lock atau GIL.

"The Python Global Interpreter Lock atau GIL, dalam kata-kata sederhana, adalah sebuah mutex (atau kunci) yang memungkinkan hanya satu thread untuk memegang kendali interpreter Python.

Ini berarti bahwa hanya satu thread yang dapat berada dalam keadaan eksekusi pada suatu waktu. Dampak dari GIL tidak terlihat oleh pengembang yang mengeksekusi program yang hanya menggunakan single-thread, tetapi dapat menjadi hambatan kinerja dalam kode yang bergantung pada CPU dan multi-threaded.

Karena Global Interpreter Lock (GIL) hanya memungkinkan satu thread untuk dieksekusi pada satu waktu, bahkan pada sistem multi-core, ia telah mendapatkan reputasi sebagai fitur "terkenal" dari Python (lihat artikel Real Python tentang GIL).

Pada awalnya, saya tidak menyadari GIL, jadi saya mulai pemrograman di python. Pada akhirnya, meskipun program saya bersifat asinkron, ia terkunci, dan tidak peduli berapa banyak thread yang saya tambahkan, saya tetap hanya mendapatkan sekitar 12-15 iterasi per detik.

Bagian utama dari fungsi asinkron di 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 menghentikan penggunaan Python dan kembali ke papan gambar…

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

Memanfaatkan aspek asinkron dari Node.js, pendekatan ini bekerja dengan baik. Untuk detail lebih lanjut tentang pemrograman asinkron di Node.js, lihat panduan RisingStack tentang pemrograman asinkron di 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 dari 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 timer ketika jumlah email dalam file sama dengan validasi yang diselesaikan. Saya menambahkan timer agar saya bisa menjalankan tolok ukur dan memastikan 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);
    });


 Kemudian saya memanggil fungsi validateRecipients. Perhatikan bahwa fungsi ini bersifat asinkron. Setelah memvalidasi bahwa infile dan outfile adalah CSV, saya menulis baris header, dan memulai timer 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 corrects 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
    }
}

Script berikut ini benar-benar merupakan inti dari program jadi saya akan memecahnya dan menjelaskan apa yang sedang 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 respons

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

  • Validasi jika reason adalah null, dan jika demikian, isikan nilai kosong (ini agar format CSV tetap konsisten, karena dalam beberapa kasus reason diberikan dalam respons)

  • Setel options dan keys untuk modul json2csv.

  • Konversi JSON ke CSV dan keluarkan (menggunakan json2csv)

  • Tulis kemajuan di terminal

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


.then(function (response) {
    response.data.results.email = String(email); 
    // Adds the email as a value/key pair to the response JSON 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 menghadapi kesalahan berikut ketika menggunakan Windows setelah sekitar 10.000 validasi:

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

Setelah melakukan penelitian lebih lanjut, ini tampaknya menjadi masalah dengan pool koneksi klien HTTP NodeJS yang tidak menggunakan kembali koneksi. Saya menemukan artikel Stackoverflow tentang masalah ini, dan setelah menggali lebih dalam, menemukan konfigurasi default yang baik untuk pustaka axios yang menyelesaikan masalah ini. Saya masih tidak yakin mengapa masalah 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.

Berita lainnya

Baca lebih lanjut dari kategori ini

A person is standing at a desk while typing on a laptop.

Platform AI-native lengkap yang dapat beradaptasi dengan pertumbuhan bisnis Anda.

Product

Solusi

Sumber Daya

Company

Segera hadir

Sosial

Newsletter

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

Daftar

© 2025 Bird

A person is standing at a desk while typing on a laptop.

Platform AI-native lengkap yang dapat beradaptasi dengan pertumbuhan bisnis Anda.

Product

Solusi

Sumber Daya

Company

Sosial

Newsletter

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

Daftar

© 2025 Bird