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.

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:

python
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:

text
id: int32
kasir: string
produk: string
qty: int32
harga: int64
[45000, 15000, 25000, 18000]
4 baris, 5 kolom

Mari kita telaah baris-baris kode di atas:

  1. Baris 4-15 (pa.table({...})): membangun table dari lima kolom; parameter type=pa.int32() dan type=pa.int64() menetapkan tipe secara eksplisit — id dan qty cukup int32, sedangkan harga yang bisa besar memakai int64.
  2. Baris 17 (pq.write_table(tabel, "transaksi.parquet")): menulis berkas Parquet dengan kompresi bawaan snappy.
  3. Baris 19-22 (pq.read_table(...)): membaca seluruh table kembali; hasil.schema membuktikan tipe data bertahan di dalam berkas, dan to_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:

python
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:

text
baris: 4
row group: 1
id -> INT32 / SNAPPY
kasir -> BYTE_ARRAY / SNAPPY
produk -> BYTE_ARRAY / SNAPPY
qty -> INT32 / SNAPPY
harga -> INT64 / SNAPPY

physical_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:

python
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:

text
CSV    : 3308922 byte
Parquet: 614558 byte
rasio  : 5.4 x lebih kecil

Parquet 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:

python
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:

text
100000 baris, 2 kolom
contoh: Teh Melati 16000

Parameter 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.