AizuDemy

Tutorial Build Embedded OLAP Engine di Node.js Pakai DuckDB & Parquet

Tutorial Build Embedded OLAP Engine di Node.js Pakai DuckDB & Parquet
IKLAN
IDCloudHost
๐ŸŽง
Dengarkan Artikel Ini
Suara AI Otomatis โ€ข 8 mnt baca baca
โšก TL;DR

Poin Kunci Artikel Ini:

  • Suasana kantor mendadak tegang.Kejadian seperti ini sangat sering dialami oleh tim pengembang backend.
  • Setiap kali ada record baru masuk, seluruh nilai kolom dalam baris tersebut disimpan berurutan di disk.
  • Saat membuat laporan total pendapatan bulanan, query SQL sebenarnya cuma butuh dua kolom: dan .
๐Ÿ“‹ Daftar Isi Materi Tutup โ–ด

Bayangkan situasi ini: jam lima sore, tinggal hitungan menit sebelum jam pulang kerja, mendadak atasan minta laporan agregasi total penjualan dari 10 juta baris data transaksi. Tanpa curiga, kamu membuka aplikasi backend Node.js, lalu mengeksekusi query SQL SELECT category, SUM(total) FROM orders GROUP BY category; langsung ke database PostgreSQL produksi.

Beberapa detik kemudian, pemakaian CPU server melesat ke angka 100%, koneksi API aplikasi lain mulai tumbang karena timeout, dan indikator loading di dashboard front-end berputar tanpa kepastian. Suasana kantor mendadak tegang.

Kejadian seperti ini sangat sering dialami oleh tim pengembang backend. Masalah dasarnya bukan karena Node.js lambat atau query SQL kamu salah, melainkan karena database transaksional (OLTP) dipaksa melakukan pekerjaan analisis data besar (OLAP). PostgreSQL dan MySQL dirancang sangat baik untuk menangani transaksi harian secara cepat, tetapi bakal megap-megap saat diminta memproses agregasi data berskala jutaan baris.

Kenapa Database Relasional Kerap Tumbang Saat Query Agregasi Data Besar?

Database relasional tradisional seperti PostgreSQL memakai penyimpanan berbasis baris (row-oriented storage). Setiap kali ada record baru masuk, seluruh nilai kolom dalam baris tersebut disimpan berurutan di disk. Model ini sangat pas ketika aplikasi butuh mengambil data satu pengguna spesifik, misalnya lewat klausa WHERE id = 123.

Namun, kebutuhan agregasi analitik punya karakter yang bertolak belakang. Saat membuat laporan total pendapatan bulanan, query SQL sebenarnya cuma butuh dua kolom: created_at dan amount. Pada database berbasis baris, sistem I/O disk tetap dipaksa membaca seluruh isi baris data dari disk ke memori RAM, termasuk kolom teks panjang seperti alamat pengiriman, catatan transaksi, sampai metadata JSON yang tidak dibutuhkan sama sekali.

Bayangkan kamu mau membeli satu buah apel di supermarket, tetapi kasir mengharuskan kamu membawa pulang seluruh rak beserta kardusnya hanya untuk memindai label harga apel tersebut. Pemborosan I/O disk dan RAM inilah yang menjadi biang kerok utama lambatnya query analitik.

  • OLTP (Online Transaction Processing): Berorientasi baris. Sangat cepat untuk operasi INSERT, UPDATE, dan DELETE per baris. Buruk untuk agregasi data masif.
  • OLAP (Online Analytical Processing): Berorientasi kolom. Hanya membaca kolom yang dipanggil query. Punya rasio kompresi data yang jauh lebih tinggi.

Cara lama untuk mengatasi masalah ini adalah membangun pipeline ETL ke Data Warehouse eksternal seperti Snowflake, Google BigQuery, atau ClickHouse. Solusi tersebut memang ampuh untuk perusahaan kelas atas, tetapi menambah biaya infrastruktur yang mahal, meningkatkan latensi jaringan, serta memperrumit arsitektur backend untuk tim berskala kecil hingga menengah.

Mengenal DuckDB dan Format Parquet

DuckDB hadir membawa pendekatan baru bernama Embedded OLAP. Kalau kamu familiar dengan SQLite yang berjalan langsung di dalam proses aplikasi tanpa perlu server database terpisah, DuckDB adalah kembaran SQLite yang dirancang khusus untuk menangani query analitik super cepat.

DuckDB dieksekusi secara in-process di dalam alokasi memori aplikasi Node.js. Tidak ada network latency, tidak ada kerumitan mengelola koneksi TCP, dan pemrosesan data dilakukan memakai vectorized execution engine yang memanfaatkan arsitektur CPU modern secara optimal.

Pasangan paling serasi untuk DuckDB adalah format file Apache Parquet. Parquet merupakan format file terkompresi berbasis kolom yang menyimpan metadata detail mengenai struktur data di dalamnya. Kombinasi DuckDB dan Parquet memberikan keuntungan teknis yang luar biasa:

  1. Projection Pushdown: DuckDB cuma membaca byte data pada kolom yang dipanggil di klausa SELECT. Kolom lain tidak akan pernah dimuat ke RAM.
  2. Predicate Pushdown: Filter seperti WHERE created_at > '2024-01-01' dievaluasi langsung di tingkat metadata file Parquet. Data block yang tidak masuk kriteria akan dilewati tanpa perlu dibaca dari disk.
  3. Kompresi Data Tinggi: Menggunakan algoritma kompresi Snappy atau ZSTD, ukuran file Parquet umumnya 70% hingga 80% lebih hemat dibanding file CSV atau JSON biasa.

Praktek Integrasi DuckDB & Parquet di Backend Node.js

Mari kita coba buat simulasi langsung untuk membangun engine analitik di backend Node.js. Kita akan mengeksekusi query agregasi atas jutaan data transaksi secara efisien.

1. Inisialisasi Proyek Node.js

Buka terminal kamu, lalu jalankan perintah berikut untuk membuat folder proyek baru dan menginstal dependensi yang dibutuhkan:

mkdir node-duckdb-analytics
cd node-duckdb-analytics
npm init -y
npm install duckdb express

2. Membuat Dataset Parquet Simulasi

Buat sebuah berkas bernama generate-data.js. Script ini bertugas membuat 3 juta baris data transaksi buatan dan menyimpannya langsung ke dalam berkas Parquet terkompresi ZSTD:

const duckdb = require('duckdb');
const db = new duckdb.Database(':memory:');

console.log('Memulai pembuatan data simulasi...');

db.all(`
  CREATE TABLE orders AS 
  SELECT 
    range AS order_id,
    (range % 500)::INTEGER AS customer_id,
    CASE WHEN range % 3 = 0 THEN 'Electronics' 
         WHEN range % 3 = 1 THEN 'Clothing' 
         ELSE 'Groceries' END AS category,
    (random() * 500)::DOUBLE AS amount,
    TIMESTAMP '2023-01-01 00:00:00' + INTERVAL (range % 365) DAY AS order_date
  FROM range(1, 3000001);
  
  COPY orders TO 'orders.parquet' (FORMAT PARQUET, COMPRESSION 'ZSTD');
`, (err) => {
  if (err) {
    console.error('Gagal membuat berkas Parquet:', err);
  } else {
    console.log('Berkas orders.parquet berisi 3 juta baris data berhasil dibuat!');
  }
});

Eksekusi file tersebut dengan perintah node generate-data.js. Dalam hitungan detik, file orders.parquet akan muncul di direktori proyek dengan ukuran yang sangat ringkas (sekitar 30-40 MB).

3. Membuat Server REST API Analitik

Selanjutnya, buat berkas server.js yang berfungsi sebagai REST API. Endpoint ini mengeksekusi SQL langsung ke file Parquet tanpa perlu melakukan impor data ke database mana pun:

const express = require('express');
const duckdb = require('duckdb');

const app = express();
const db = new duckdb.Database(':memory:');
const con = db.connect();

app.get('/api/analytics/sales-summary', (req, res) => {
  const startTime = Date.now();
  
  const query = `
    SELECT 
      category,
      COUNT(order_id) AS total_orders,
      ROUND(SUM(amount), 2) AS total_revenue,
      ROUND(AVG(amount), 2) AS avg_order_value
    FROM 'orders.parquet'
    GROUP BY category
    ORDER BY total_revenue DESC
  `;

  con.all(query, (err, result) => {
    if (err) {
      return res.status(500).json({ status: 'error', message: err.message });
    }
    
    const durationMs = Date.now() - startTime;
    res.json({
      execution_time_ms: durationMs,
      total_rows_processed: 3000000,
      data: result
    });
  });
});

app.listen(3000, () => {
  console.log('Server Analytics berjalan di http://localhost:3000');
});

Jalankan node server.js lalu panggil endpoint http://localhost:3000/api/analytics/sales-summary melalui browser atau Postman. Hasil kalkulasi dari 3 juta data transaksi akan tersaji dalam waktu di bawah 50 milidetik.

4. Membaca File Parquet Langsung dari Cloud Storage (AWS S3 / HTTP)

DuckDB tidak cuma bisa membaca berkas di harddisk lokal. Lewat ekstensi httpfs, Node.js dapat membaca file Parquet langsung dari link HTTPS atau bucket AWS S3 tanpa perlu mengunduh seluruh isi berkas secara manual:

con.all(`
  INSTALL httpfs;
  LOAD httpfs;
  
  SELECT category, SUM(amount) AS total 
  FROM 'https://my-data-bucket.s3.amazonaws.com/analytics/orders.parquet'
  WHERE order_date >= '2023-06-01'
  GROUP BY category;
`, (err, result) => {
  if (err) console.error(err);
  else console.log('Hasil query S3:', result);
});

DuckDB secara pintar memakai fitur HTTP Range Requests untuk menarik byte metadata yang diperlukan saja, sehingga konsumsi bandwidth jaringan tetap hemat.

Optimasi Performa & Pencegahan Memory Leak

Meskipun DuckDB sangat cepat, menjalankannya di lingkungan Node.js tetap butuh perhatian ekstra pada manajemen RAM. DuckDB beroperasi di level native C++, yang berarti alokasi memorinya berada di luar V8 Heap milik Node.js.

1. Batasi Penggunaan Memori Native

Secara default, DuckDB akan berusaha mengalokasikan seluruh sisa RAM server. Di lingkungan server produksi seperti Container Docker atau AWS ECS, hal ini bisa memicu Out-Of-Memory (OOM) Killer dari sistem operasi. Batasi alokasi RAM dan jumlah thread CPU dengan perintah PRAGMA:

con.exec("PRAGMA max_memory='2GB'");
con.exec("PRAGMA threads=4");

2. Hindari Memuat Seluruh Result Set ke V8 Heap

Fungsi con.all() akan mengonversi seluruh baris data hasil query menjadi array objek JavaScript. Jika query kamu mengembalikan 500.000 baris data mentah ke Node.js, V8 heap akan mendadak bengkak dan memicu Garbage Collection (GC) pause yang membuat server macet sementara.

Gunakan prinsip ini: lakukan agregasi di tingkat SQL (menggunakan GROUP BY, SUM, AVG) agar data yang kembali ke Node.js berukuran kecil, atau gunakan teknik streaming jika data hasil query memang harus ditransmisikan dalam jumlah banyak.

3. Pakai Pola Singleton Connection

Membuat objek koneksi baru memakai db.connect() di setiap HTTP request yang masuk adalah praktik buruk yang bisa menyebabkan kebocoran memori (memory leak) pada native handle. Buatlah satu instance koneksi tunggal yang dipakai bersama:

class AnalyticsDatabase {
  constructor() {
    if (!AnalyticsDatabase.instance) {
      this.db = new duckdb.Database(':memory:');
      this.connection = this.db.connect();
      this.connection.exec("PRAGMA max_memory='1GB'");
      AnalyticsDatabase.instance = this;
    }
    return AnalyticsDatabase.instance;
  }

  query(sql) {
    return new Promise((resolve, reject) => {
      this.connection.all(sql, (err, rows) => {
        if (err) reject(err);
        else resolve(rows);
      });
    });
  }
}

module.exports = new AnalyticsDatabase();

Kapan Harus Memakai DuckDB di Node.js?

DuckDB bukan dibuat untuk menggantikan database utama aplikasi kamu, melainkan sebagai mesin pelengkap untuk mempercepat pemrosesan data analitik.

KriteriaDuckDB + ParquetDatabase OLTP (PostgreSQL/MySQL)
Beban Kerja UtamaQuery analitik, pembuat laporan, BI dashboardTransaksi real-time, operasi CRUD user
Model PenyimpananFile Parquet terkompresi (Columnar)Tabel berorientasi baris (Row-based)
Pola Akses DataMembaca jutaan baris pada sedikit kolomMembaca atau menulis 1 baris data secara utuh
Arsitektur ServerEmbedded (Tanpa server database terpisah)Dedicated Database Server / Cluster

Arsitektur DuckDB dan Parquet sangat tepat dipakai saat aplikasi Node.js kamu perlu menyediakan API untuk dashboard analitik internal, memproses log sistem berskala besar, atau mengolah file ekspor data tanpa mengganggu kinerja database operasional utama.

Kesimpulan

Kombinasi DuckDB dan Apache Parquet memberikan kemampuan pemrosesan analitik berskala besar langsung dari dalam backend Node.js tanpa perlu membayar infrastruktur Data Warehouse yang mahal dan rumit.

Langkah Praktis: Coba ambil satu tabel transaksi berukuran besar yang ada di database kamu, lalu ekspor ke format file Parquet. Jalankan query agregasi memakai DuckDB di Node.js, dan perhatikan seberapa jauh perbedaan kecepatan serta penurunan beban I/O pada database utama kamu!

A
Aizu Dev

Tim penulis AizuDemy yang menyajikan tutorial teknologi, cloud, dan pengembangan perangkat lunak dalam Bahasa Indonesia.

๐Ÿ“– Artikel Terkait