Tag: Large Dataset Analysis

  • Memadukan DuckDB dan Python untuk Pivot Table

    Saya dapat saran dari beberapa kawan (thanks kang Zam dan mas Bernard) pada artikel memproses 600 ribu baris data kemarin untuk menggunakan library Pandas dan DuckDB supaya pemrosesan datanya lebih cepat dan efisien.

    Setelah melakukan riset beberapa saat, saya memutuskan untuk mencobanya meskipun baru dilakukan di branch untuk melihat hasilnya terlebih dulu sebelum merge ke master.

    Apa yang saya dapatkan?

    Hasilnya, DuckDB (dengan dikombinasikan dengan Pandas) ini membuat aplikasi saya bisa berjalan dengan lebih cepat. Saya memang tidak melakukan benchmark dengan detil ya, saya cuma membandingkan waktu pemrosesan saja, dimana untuk 600 ribu data ini sekarang bisa diproses 30 detik lebih cepat.

    Ini sangat bagus, karena 600 ribu data ini baru permulaan, nantinya akan bisa bertambah dan saya prediksi di akhir tahun nanti bisa tembus 4 juta baris. Tentunya kalau masih diproses dengan metode streaming aggregation akan membuat OOM lagi.

    Pertanyaan muncul: Haruskah semua logika dilakukan di dalam SQL (DuckDB), ataukah lebih baik menyerahkannya ke Python? Kasusnya sederhana: Saya harus memproses data transaksi (CSV), melakukan filter, agregasi, dan menyajikannya dalam bentuk Pivot Table (per bulan).

    Inilah alasan mengapa strategi “DuckDB untuk Heavy Lifting, Python untuk Reshaping” adalah pemenangnya.

    Saya membagi proses menjadi dua tahap kritis:

    1. Tahap Otot (DuckDB)

    Saya menyerahkan tugas berat ke DuckDB. Mengapa? Karena DuckDB adalah database columnar yang menggunakan eksekusi vektor (C++). Ia sangat cepat dalam memindai ribuan baris.

    • Tugas: Filter data (buang beberapa data yang tidak diperlukan) dan lakukan SUM(total_bill) dengan GROUP BY.
    • Hasil: Data mentah sebanyak 600 ribu baris dipangkas menjadi hanya ~100-500 baris data agregat.

    Sebelum data masuk ke DuckDB, ada satu optimasi lagi yang saya terapkan di level CSV parsing. Alih-alih menggunakan csv.reader bawaan Python lalu menyisipkan baris per baris dengan executemany(), saya menggunakan kombinasi Pandas + Apache Arrow protocol.

    Baca CSV dengan pandas, hanya kolom yang diperlukan

    df = pd.read_csv(
    buffer,
    usecols=[0, 5, 9, 10, 13, 16], # invoice_month, sku, total_bill, source, service_group, bill_org
    dtype=str,
    )

    Bulk insert via Arrow fast-path (zero-copy)

    conn.register("_temp_csv", df)
    conn.execute("INSERT INTO billing SELECT * FROM _temp_csv")

    Apa keuntungannya?

    1. Column Pruning — Dari 17 kolom di CSV, saya hanya membaca 6 kolom yang diperlukan. Kolom lain di-skip sejak awal, menghemat memori dan waktu parsing.
    2. Zero-Copy Transfer — DuckDB menerima DataFrame dari Pandas via Apache Arrow protocol tanpa menyalin data (zero-copy). Ini jauh lebih cepat dibanding executemany() yang melakukan iterasi Python-level untuk setiap baris.
    3. Vectorized Parsing — Parser CSV-nya Pandas ditulis dalam C/Cython, sehingga jauh lebih cepat dari csv.reader murni Python.

    Dengan kombinasi ini, pipeline dari “file CSV di Google Drive” hingga “siap di-query di DuckDB” menjadi sepenuhnya vektorisasi, tidak ada lagi Python-level loop di tahap loading.

    2. Tahap Otak/Fleksibilitas (Python)

    Setelah data menjadi kecil, saya menggunakan Python (defaultdict) untuk melakukan pivoting (mengubah baris menjadi kolom bulan).

    • Tugas: Mengatur ulang data dari bentuk (key, month, total) menjadi struktur {key: {month: total}}.

    Mungkin Anda bertanya, “DuckDB kan punya fungsi PIVOT, kenapa tidak dipakai saja?” Jawabannya adalah Dynamic Columns.

    1. Masalah Compile-Time: SQL PIVOT biasanya perlu tahu nama kolomnya dari awal (misal: Januari, Februari, dst). Jika bulan bertambah, kueri SQL harus diubah secara dinamis. Ini menambah kompleksitas kode (Dynamic SQL generation).
    2. Efisiensi Manusia vs Mesin: Menulis generator SQL yang kompleks hanya untuk menghemat beberapa milidetik pada data yang sudah kecil (~500 baris) tidaklah sebanding. Python jauh lebih fleksibel dalam menangani kunci (bulan) baru secara otomatis.
    3. Maintainability: Menggunakan defaultdict di Python jauh lebih mudah dibaca dan di-debug oleh rekan tim lain dibandingkan kueri SQL yang panjang dan penuh dengan fungsi EXECUTE.

    Kesimpulan: Menemukan “Sweet Spot“

    Analisis saya menyimpulkan bahwa pendekatan ini sudah optimal.

    • Kecepatan: Didapat dari DuckDB yang menghancurkan data besar.
    • Fleksibilitas: Didapat dari Python yang menyusun tampilan akhir tanpa peduli berapa banyak kolom bulan yang muncul.

    Pelajaran bagi kita: Jangan memaksakan satu alat untuk melakukan segalanya. Gunakan database untuk agregasi, dan gunakan bahasa pemrograman untuk formatting.