Data Formats (CSV, Parquet, JSON Lines) — Data Engineering

Format Data untuk Data Engineering Pemilihan format data yang tepat sangat mempengaruhi performa query, ukuran storage, dan kemudahan processing. CSV…

Format data adalah cara baris-baris tabel ditulis ke file, supaya program lain bisa membacanya kembali dengan isi dan tipe yang sama. Bayangin catatan penjualan warung. Cara pertama: tiap penjualan satu baris di kertas, kolomnya dipisah koma. Cara kedua: tiap penjualan satu formulir kecil yang semua kotaknya berlabel. Cara ketiga: buku besar yang dipisah per kolom: satu halaman berisi semua kota, satu halaman berisi semua jumlah buku, dan sampulnya mencatat angka terkecil dan terbesar di tiap halaman. Itulah CSV, JSON Lines, dan Parquet.

🧪 Tebakan cepat: extract Toko Buku, 19 baris, disimpan sebagai CSV dan sebagai Parquet. Mana yang lebih kecil? Kalau datanya 1 juta baris, mana yang lebih kecil? Tebak dulu, jawabannya ada di bagian "Ukuran tergantung jumlah baris".

Satu extract, tiga file

Datanya order_lines: 19 baris pesanan Toko Buku yang sudah disambung dengan pelanggan dan bukunya (sama dengan raw_order_lines di pelajaran ETL vs ELT), 9 kolom. Kita tulis pakai DuckDB: database analitik yang jalan di laptop sebagai satu program, dan bisa membaca serta menulis file langsung dengan SQL.

COPY order_lines TO 'order_lines.csv' (HEADER);
COPY order_lines TO 'order_lines.jsonl' (FORMAT json);
COPY order_lines TO 'order_lines.parquet' (FORMAT parquet);

Tiap COPY ... TO menulis seluruh tabel ke satu file. HEADER menaruh nama kolom di baris pertama CSV. FORMAT json di DuckDB menulis satu object per baris. Semua angka di pelajaran ini diukur dengan DuckDB 1.5.6.

CSV: teks yang dipisah koma

CSV (Comma-Separated Values) cuma teks. Ini empat baris pertamanya, plus baris pesanan Maya:

item_id,order_id,created_at,customer,city,book_id,title,qty,price
1,1,2026-09-28 09:15:00,Budi Santoso,Jakarta,1,Laskar Pelangi,1,89000
2,2,2026-09-28 13:02:00,"Siti Rahma, S.Kom",Bandung,2,Bumi Manusia,1,125000
3,2,2026-09-28 13:02:00,"Siti Rahma, S.Kom",Bandung,3,Cantik Itu Luka,1,110000
11,8,2026-10-04 20:30:00,Maya Sari,,1,Laskar Pelangi,1,89000

Tiga kesalahan yang sering terjadi, dibaca balik dengan SELECT SUM(qty * price) AS omzet FROM 'order_lines.csv':

File-nya ditulis DuckDB membaca price sebagai Hasil omzet
benar: 89000, nama berkoma dikutip BIGINT 3319000
harga gaya Indonesia: 89.000 DOUBLE, titik dianggap desimal 3319.0
harga untuk manusia: Rp 89.000 VARCHAR (teks) Binder Error: No function matches the given name and argument types '*(BIGINT, VARCHAR)'
nama berkoma tanpa kutip DuckDB gagal menebak pemisahnya dan membaca tiap baris jadi 1 kolom teks Binder Error: Referenced column "qty" not found in FROM clause!

Yang paling bahaya baris kedua: gak ada error, omzet Rp 3.319.000 diam-diam jadi 3319. Simpan angka sebagai angka polos, tanpa titik ribuan dan tanpa "Rp".

Satu lagi: simpan CSV sebagai UTF-8. Excel dengan pilihan "CSV UTF-8" menaruh 3 byte penanda (BOM) di awal file. DuckDB dan pandas membuangnya, tapi modul csv bawaan Python dengan encoding="utf-8" membaca kolom pertama jadi 'item_id'. Pakai encoding="utf-8-sig".

JSON Lines: satu object per baris

JSON Lines (.jsonl, juga disebut NDJSON) menyimpan satu object JSON utuh per baris:

{"item_id":1,"order_id":1,"created_at":"2026-09-28 09:15:00","customer":"Budi Santoso","city":"Jakarta","book_id":1,"title":"Laskar Pelangi","qty":1,"price":89000}
{"item_id":11,"order_id":8,"created_at":"2026-10-04 20:30:00","customer":"Maya Sari","city":null,"book_id":1,"title":"Laskar Pelangi","qty":1,"price":89000}

Parquet: disimpan per kolom

CSV dan JSON Lines itu row-based: baris 1 lengkap, lalu baris 2 lengkap, dan seterusnya. Parquet itu columnar: semua nilai satu kolom ditulis berdampingan. Isi file order_lines.parquet menurut parquet_metadata() DuckDB:

Bagian Isinya di file kita
Row group potongan baris; di sini 1 row group berisi 19 baris (DuckDB membuat satu per sekitar 122 ribu baris)
Column chunk satu per kolom per row group, dikompresi SNAPPY; qty cuma 66 byte
Encoding city dan qty pakai dictionary: tiap nilai berbeda disimpan sekali, barisnya cuma menyimpan nomor urutnya
Footer schema (price = INT64, created_at = TIMESTAMP_MICROS) dan statistik per kolom: city punya 2 NULL, price min 89000 max 950000

Lihat statistik price: salah ketik 950000 sudah kelihatan dari metadata, tanpa membaca satu baris pun. Struktur ini memberi dua hal:

Tipe-tipenya tersimpan di file, jadi gak ada tebak-tebakan seperti CSV. Kekurangannya: Parquet itu biner, gak bisa dibuka di editor teks atau Excel, dan file yang sudah ditulis gak bisa ditambah baris; data baru jadi file baru.

Ukuran tergantung jumlah baris

Format 19 baris 1 juta baris GROUP BY city di 1 juta baris
CSV 1.449 byte 81,4 MB 0,59 detik
JSON Lines 3.167 byte 175,4 MB 0,45 detik
Parquet (snappy) 2.153 byte 13,1 MB 0,009 detik
Parquet (zstd) 2.101 byte 4,9 MB

Data 1 juta barisnya kolom dan isinya sama: 7 pelanggan dan 4 buku yang diulang, diukur dengan DuckDB di laptop. Di 19 baris, CSV paling kecil: footer Parquet saja sudah 1.038 byte. Di 1 juta baris, Parquet 6 kali lebih kecil dari CSV, 16 kali dengan kompresi zstd, dan query-nya puluhan kali lebih cepat. Itu jawaban tebakannya: dua-duanya, tergantung jumlah barisnya.

💡 Avro dan ORC

Avro itu row-based seperti JSON Lines, tapi biner dan ringkas: schema-nya (ditulis dalam JSON) disimpan sekali di awal file, lalu tiap record ditulis tanpa nama kolom. Avro jago menangani perubahan schema, misalnya menambah kolom dengan nilai default, jadi sering dipakai untuk pesan Kafka bersama Schema Registry.

ORC (Optimized Row Columnar) itu columnar seperti Parquet, lahir di ekosistem Hadoop dan Hive. Strukturnya mirip (potongan baris yang disebut stripe, statistik, kompresi per kolom). ORC masih umum di Hive, Trino, dan Spark; di luar dunia Hadoop, Parquet jauh lebih banyak dipakai.

⚠️ Jebakan yang sering ketemu

TL;DR: CSV itu teks berpemisah koma tanpa tipe: nilai berkoma harus dikutip, NULL dan teks kosong tercampur, dan angka gaya Indonesia (89.000) terbaca salah tanpa error. JSON Lines menyimpan satu object JSON per baris, jadi bisa diproses baris demi baris (cocok untuk log dan streaming) dan tipenya ikut, tapi ukurannya paling besar. Parquet menyimpan data per kolom dalam row group, dengan encoding dictionary, kompresi, dan footer berisi schema serta statistik min/max, jadi query cuma membaca kolom dan row group yang perlu. Di 19 baris CSV paling kecil (1.449 byte); di 1 juta baris Parquet 13,1 MB lawan CSV 81,4 MB. Avro itu biner per baris dengan schema (Kafka), ORC itu columnar dari dunia Hadoop.

Yang akan kamu pelajari