Query analitik punya pola yang sangat khas: SELECT produk, harga dari jutaan baris — hanya dua kolom dari belasan yang ada. Pada format berbasis baris seperti CSV dan Avro, mesin tetap membaca seluruh baris lalu membuang kolom yang tidak perlu. Apache Parquet membalik penyimpanannya: data disimpan per kolom sehingga mesin cukup membaca kolom yang diminta. Karena keunggulan ini, Parquet menjadi format simpanan standar di data warehouse dan lakehouse modern — Spark, DuckDB, BigQuery, hingga Snowflake semuanya memahaminya, seperti dijelaskan dokumentasi resmi Parquet
.
Columnar vs Row-Based #
Perbedaan mendasar kedua gaya penyimpanan terlihat pada ilustrasi berikut:
Pada format baris, nilai-nilai satu transaksi bersebelahan; pada format kolom, nilai-nilai satu kolom bersebelahan. Susunan kedua ini membuat data satu kolom cenderung serupa — kasir hanya berisi tiga nama yang berulang — sehingga kompresi bekerja jauh lebih efektif.
Menulis dan Membaca Table dengan pyarrow #
Implementasi Python untuk Parquet adalah pyarrow
. Data dibungkus dalam pa.table berupa kolom-kolom bertipe eksplisit — tipe ini tersimpan permanen di dalam berkas, berbeda dengan CSV yang harus dikonversi setiap kali dibaca:
import pyarrow as pa
import pyarrow.parquet as pq
tabel = pa.table(
{
"id": pa.array([1, 2, 3, 4], type=pa.int32()),
"kasir": pa.array(["Budi", "Siti", "Budi", "Dewi"], type=pa.string()),
"produk": pa.array(
["Kopi Robusta", "Teh Melati", "Gula Aren, 500g", "Susu UHT 1L"],
type=pa.string(),
),
"qty": pa.array([2, 3, 1, 2], type=pa.int32()),
"harga": pa.array([45000, 15000, 25000, 18000], type=pa.int64()),
}
)
pq.write_table(tabel, "transaksi.parquet")
hasil = pq.read_table("transaksi.parquet")
print(hasil.schema)
print(hasil.to_pydict()["harga"])
print(hasil.num_rows, "baris,", hasil.num_columns, "kolom")Output yang Dihasilkan:
id: int32
kasir: string
produk: string
qty: int32
harga: int64
[45000, 15000, 25000, 18000]
4 baris, 5 kolomMari kita telaah baris-baris kode di atas:
- Baris 4-15 (
pa.table({...})): membangun table dari lima kolom; parametertype=pa.int32()dantype=pa.int64()menetapkan tipe secara eksplisit —iddanqtycukupint32, sedangkanhargayang bisa besar memakaiint64. - Baris 17 (
pq.write_table(tabel, "transaksi.parquet")): menulis berkas Parquet dengan kompresi bawaansnappy. - Baris 19-22 (
pq.read_table(...)): membaca seluruh table kembali;hasil.schemamembuktikan tipe data bertahan di dalam berkas, danto_pydict()mengembalikan data sebagai dictionary kolom — integer tetap integer tanpa konversi manual.
Row Group dan Kompresi per Kolom #
Secara fisik, berkas Parquet dibagi menjadi row group — blok berisi sekelompok baris yang masing-masing menyimpan data per kolom. Metadata berkas memuat statistik tiap kolom dan jenis kompresinya:
import pyarrow.parquet as pq
meta = pq.ParquetFile("transaksi.parquet").metadata
print("baris:", meta.num_rows)
print("row group:", meta.num_row_groups)
grup = meta.row_group(0)
for indeks in range(grup.num_columns):
kolom = grup.column(indeks)
print(kolom.path_in_schema, "->", kolom.physical_type, "/", kolom.compression)Output yang Dihasilkan:
baris: 4
row group: 1
id -> INT32 / SNAPPY
kasir -> BYTE_ARRAY / SNAPPY
produk -> BYTE_ARRAY / SNAPPY
qty -> INT32 / SNAPPY
harga -> INT64 / SNAPPYphysical_type adalah representasi fisik di dalam berkas (INT32, INT64, BYTE_ARRAY) dan seluruh kolom terkompresi SNAPPY secara bawaan. Parameter compression pada pq.write_table() juga menerima dictionary per kolom, misalnya compression={"harga": "zstd"}, sehingga kolom yang jarang dibaca bisa dikompresi lebih agresif. Row group memungkinkan pembacaan paralel: tiap task Spark atau DuckDB mengambil row group berbeda.
Ukuran File: CSV vs Parquet #
Ukuran nyata paling meyakinkan. Kita buat dataset dummy 100.000 transaksi (nilai berulang dengan pola modulo, meniru data transaksi toko sesungguhnya), lalu tulis ke CSV dan Parquet:
import csv
import os
import pyarrow as pa
import pyarrow.parquet as pq
n = 100_000
produk_list = ["Kopi Robusta", "Teh Melati", "Gula Aren, 500g", "Susu UHT 1L", "Café Latte"]
kasir_list = ["Budi", "Siti", "Dewi"]
baris = [
(i, kasir_list[i % 3], produk_list[i % 5], 1 + i % 5, 15000 + (i % 20) * 1000)
for i in range(1, n + 1)
]
with open("transaksi_besar.csv", "w", newline="", encoding="utf-8") as berkas:
tulis = csv.writer(berkas)
tulis.writerow(["id", "kasir", "produk", "qty", "harga"])
tulis.writerows(baris)
tabel_besar = pa.table(
{
"id": [b[0] for b in baris],
"kasir": [b[1] for b in baris],
"produk": [b[2] for b in baris],
"qty": [b[3] for b in baris],
"harga": [b[4] for b in baris],
}
)
pq.write_table(tabel_besar, "transaksi_besar.parquet")
ukuran_csv = os.path.getsize("transaksi_besar.csv")
ukuran_pq = os.path.getsize("transaksi_besar.parquet")
print("CSV :", ukuran_csv, "byte")
print("Parquet:", ukuran_pq, "byte")
print("rasio :", round(ukuran_csv / ukuran_pq, 1), "x lebih kecil")Output yang Dihasilkan:
CSV : 3308922 byte
Parquet: 614558 byte
rasio : 5.4 x lebih kecilParquet hanya memakai sekitar 18% ukuran CSV untuk dataset yang sama. Penyimpanan per kolom membuat kompresi menemukan pola berulang, dan angka tidak lagi menempati satu karakter per digit.
Predicate Pushdown dan Column Pruning #
Dua teknik inilah alasan Parquet menjadi standar warehouse. Column pruning berarti hanya kolom yang disebut query yang dibaca dari disk; predicate pushdown berarti statistik row group (nilai minimum-maksimum per kolom) dipakai untuk melewati row group yang pasti tidak memuat data yang dicari:
import pyarrow.parquet as pq
hasil = pq.read_table("transaksi_besar.parquet", columns=["produk", "harga"])
print(hasil.num_rows, "baris,", hasil.num_columns, "kolom")
data = hasil.to_pydict()
print("contoh:", data["produk"][0], data["harga"][0])Output yang Dihasilkan:
100000 baris, 2 kolom
contoh: Teh Melati 16000Parameter columns=["produk", "harga"] membuktikan pruning: 100.000 baris utuh kembali, tetapi hanya 2 kolom yang dibaca — tiga kolom lainnya tidak pernah disentuh. Pada query nyata dengan filter seperti harga > 40000, engine membandingkan syarat tersebut dengan statistik tiap row group dan langsung melewati row group yang statistiknya berada di luar rentang.
Parquet bukan satu-satunya format kolom di ekosistem Hadoop — saingan seniornya bernama ORC, yang kita bahas di chapter berikutnya.