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
- Nilai yang berisi koma dibungkus tanda kutip:
"Siti Rahma, S.Kom". Tanpa kutip, koma di namanya dibaca sebagai pemisah kolom. - Gak ada tipe.
89000cuma lima karakter; pembacanya yang menebak itu angka. - Kota Maya yang
NULLjadi kosong di antara dua koma. Teks kosong""juga ditulis kosong, jadi CSV gak bisa membedakan keduanya.
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}
- Tiap baris JSON yang valid sendiri. Filenya bukan satu array besar, jadi program bisa membaca dan memproses baris demi baris tanpa mem-parse seluruh file, dan baris baru tinggal ditambahkan di akhir. Itu sebabnya JSON Lines dipakai untuk log, event, dan data yang mengalir terus (streaming).
- Tipenya ikut:
89000angka,"Jakarta"teks,nulltetapnull, beda dengan"". - Harganya ukuran: nama kolom diulang di tiap baris. 3.167 byte, dua kali CSV-nya. Tanggal tetap teks.
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:
- Column pruning:
SELECT city, SUM(qty) ...cuma membaca chunkcitydanqty. Kolom lain gak disentuh. - Predicate pushdown:
WHERE created_at >= '2026-10-05'melewati row group yangcreated_atmaksimumnya lebih kecil, cukup dengan membaca footer.
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
- Angka diformat untuk manusia.
89.000terbaca 89,Rp 89.000jadi teks. Tulis angka polos; format rupiah urusan tampilan. - CSV ditulis dengan menyambung string pakai koma. Nama seperti
Siti Rahma, S.Kommerusak kolomnya. Pakai library CSV yang mengutip nilai berkoma. NULLhilang di CSV. Kosong bisa berartiNULLatau teks kosong. Kalau bedanya penting, pakai JSON Lines atau Parquet.- Ribuan file Parquet kecil. Tiap file bawa footer sendiri, dan membuka ribuan file lebih lambat dari membaca beberapa file besar. Gabungkan per hari atau per minggu.
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.