Parquet adalah format berkas kolom yang menjadi standar penyimpanan tabel analitik di data lake dan warehouse modern. Cara format ini menata data di dalam berkasnya menentukan seberapa cepat query berjalan dan seberapa kecil ruang simpan yang terpakai. Topik ini membedah anatomi Parquet, yaitu penyimpanan kolom, kompresi, dan partisi, lalu menunjukkan dampaknya dengan pyarrow pada berkas contoh.
Penyimpanan baris versus kolom#
Format baris seperti CSV dan Avro menata nilai satu baris secara berdampingan, sedangkan format kolom seperti Parquet menata nilai satu kolom secara berurutan. Perbedaan tata letak ini paling terasa pada query analitik yang biasanya membaca sedikit kolom dari banyak baris. Pada format baris, membaca satu kolom tetap menarik seluruh baris dari berkasnya, sedangkan format kolom cukup membaca bagian kolom yang diminta saja.
Tata letak kolom juga menguntungkan kompresi. Nilai satu kolom memiliki tipe dan pola yang serupa, sehingga nilai yang berdekatan mudah dipadatkan. CSV tidak mendapat keuntungan ini karena angka, teks, dan pemisah berselang-seling di sepanjang berkasnya. Selisih ukuran antara kedua format biasanya beberapa kali lipat untuk data tabular yang sama.
Konsekuensinya, format kolom kurang cocok untuk membaca atau menulis satu baris secara utuh, misalnya pada beban kerja transaksi. Parquet karenanya ditempatkan untuk penyimpanan analitik, bukan pengganti basis data operasional. Penempatan ini selaras dengan pola akses pipeline data, yaitu ditulis sekali dan dibaca banyak kali dengan query yang berbeda-beda.
Anatomi berkas Parquet#
Sebuah berkas Parquet tersusun dari row group, yakni sekelompok baris yang disimpan bersama. Di dalam satu row group, nilai tiap kolom disimpan berurutan sebagai column chunk, yakni rangkaian nilai satu kolom untuk kelompok baris tersebut. Chunk itu terbagi lagi menjadi page, yakni unit terkecil yang dikompresi. Tata letak bertingkat ini menjadi dasar seluruh keunggulan pembacaan Parquet.
Di akhir berkas terdapat footer, yaitu bagian metadata yang menyimpan schema, lokasi setiap column chunk, serta statistik seperti nilai minimum dan maksimum per chunk. Urutan inilah yang membuat Parquet dapat dibaca secara selektif. Program pembaca membaca footer terlebih dahulu, menentukan chunk mana yang relevan, lalu melompat langsung ke posisi byte-nya. Berkasnya tidak perlu dibaca dari awal sampai akhir.
Dua keputusan pada tingkat query muncul dari struktur tersebut. Column pruning, yakni pemangkasan kolom, berarti pembacaan hanya dilakukan pada chunk kolom yang diminta. Predicate pushdown memakai statistik minimum dan maksimum pada footer untuk melewati chunk yang pasti tidak memuat baris yang dicari. Keduanya tidak memerlukan perubahan pada berkasnya, melainkan hanya pada cara query menyatakannya.
Menulis dan membaca dengan pyarrow#
Pustaka pyarrow menulis dan membaca Parquet melalui modul pyarrow.parquet, dan contoh berikut mengubah sebuah CSV menjadi Parquet lalu membaca kembali satu kolom saja.
import os
import pyarrow.csv
import pyarrow.parquet as pq
# 1. susun data catatan sensor suhu secara deterministik
baris = ["id_pengukuran,sensor,suhu_c,kelembapan"]
for i in range(3000):
id_pengukuran = f"UKR-{i:06d}"
sensor = f"Ruang-{i % 3 + 1}"
suhu = 24 + (i % 8) * 0.5
kelembapan = 55 + (i % 10)
baris.append(f"{id_pengukuran},{sensor},{suhu},{kelembapan}")
with open("suhu.csv", "w", encoding="utf-8") as f:
f.write("\n".join(baris) + "\n")
# 2. tulis isi CSV yang sama sebagai berkas Parquet
tabel = pyarrow.csv.read_csv("suhu.csv")
pq.write_table(tabel, "suhu.parquet")
# 3. baca kembali satu kolom saja (column pruning)
kolom = pq.read_table("suhu.parquet", columns=["kelembapan"])
print("kolom yang dibaca :", kolom.column_names)
print("jumlah baris :", kolom.num_rows)
print("contoh nilai :", kolom.column("kelembapan").to_pylist()[:8])
# 4. schema yang tersimpan pada metadata footer
print(pq.read_schema("suhu.parquet"))
# 5. bandingkan ukuran berkasnya
print(f"ukuran suhu.csv : {os.path.getsize('suhu.csv')} byte")
print(f"ukuran suhu.parquet : {os.path.getsize('suhu.parquet')} byte")kolom yang dibaca : ['kelembapan']
jumlah baris : 3000
contoh nilai : [55, 56, 57, 58, 59, 60, 61, 62]
id_pengukuran: string
sensor: string
suhu_c: double
kelembapan: int64
ukuran suhu.csv : 81039 byte
ukuran suhu.parquet : 21219 byteLangkah ketiga adalah inti demonstrasi column pruning. read_table dengan parameter columns hanya membaca chunk kolom kelembapan, sedangkan chunk kolom lainnya tidak disentuh sama sekali. Schema yang dicetak pada langkah keempat berasal dari metadata footer, sehingga Parquet digolongkan sebagai format yang mendeskripsikan dirinya sendiri (self-describing), yakni format yang membawa definisi kolom dan tipenya tanpa berkas tambahan.
Perbandingan ukuran pada langkah kelima menunjukkan Parquet jauh lebih kecil daripada CSV untuk isi data yang sama. Ukuran byte yang persis bergantung pada versi pyarrow, codec, dan isi datanya, tetapi arah selisihnya stabil, yaitu kolom dengan nilai berulang terkompresi rapat. Angka pada keluaran ini dibacakan sebagai gambaran, bukan sebagai tolok ukur yang tetap.
Partisi direktori#
Partisi membagi satu dataset ke banyak berkas di dalam direktori berdasarkan nilai kolom, sehingga query yang menyaring kolom itu dapat melewati direktori yang tidak relevan. Partisi berada di atas struktur berkas, sehingga pemangkasannya terjadi sebelum satu berkas pun dibuka.
from pathlib import Path
import pyarrow as pa
import pyarrow.dataset as ds
# tabel kecil dengan kolom partisi ruangan
tabel = pa.table({
"id_sensor": ["S1", "S2", "S3", "S4", "S5", "S6"],
"ruangan": ["Ruang-A", "Ruang-A", "Ruang-B", "Ruang-B", "Ruang-C", "Ruang-C"],
"suhu_c": [24.0, 24.5, 25.0, 25.5, 26.0, 26.5],
})
# skema partisi bergaya Hive, yaitu direktori dengan pola kunci=nilai
partisi = ds.partitioning(pa.schema([("ruangan", pa.string())]), flavor="hive")
# tulis dataset terpartisi, satu direktori per nilai ruangan
ds.write_dataset(
tabel,
"sensor-partisi",
format="parquet",
partitioning=partisi,
)
# daftar direktori partisi yang terbentuk
for d in sorted(Path("sensor-partisi").iterdir()):
print(d.name)
# baca ulang dengan filter, hanya direktori terkait yang diperiksa
dataset = ds.dataset("sensor-partisi", format="parquet", partitioning=partisi)
hasil = dataset.to_table(filter=ds.field("ruangan") == "Ruang-A")
print("baris Ruang-A:", hasil.num_rows)ruangan=Ruang-A
ruangan=Ruang-B
ruangan=Ruang-C
baris Ruang-A: 2write_dataset menulis satu direktori per nilai kolom ruangan dengan pola penamaan kunci dan nilai bergaya Hive, yaitu ruangan=Ruang-A dan seterusnya. Pembacaan ulang dengan filter ds.field("ruangan") == "Ruang-A" hanya membuka berkas di dalam direktori terkait, dan perilaku ini disebut partition pruning, yakni pemangkasan partisi. Filter pada kolom lain tetap diuji ke setiap berkas, sedangkan filter pada kolom partisi menyaring direktorinya terlebih dahulu.
Granularitas partisi adalah keputusan desain. Partisi yang terlalu halus, misalnya per menit, menghasilkan banyak berkas kecil yang justru memperlambat pembacaan. Partisi yang terlalu kasar mengurangi manfaat pemangkasannya. Pilih kolom dan tingkatannya sesuai pola filter yang benar-benar dipakai query, dan tanggal serta wilayah menjadi pilihan yang lazim.
Kesalahan umum#
Kesalahan berikut muncul ketika Parquet diadopsi tanpa memperhatikan karakternya, dan setiap butir menyertakan perbaikannya.
- Menyimpan data kecil sebagai Parquet demi mengikuti tren. Overhead metadata dapat melebihi isi datanya sehingga ukurannya malah lebih besar daripada CSV. Perbaikannya adalah mempertahankan CSV untuk data kecil dan berpindah ke Parquet setelah data tumbuh.
- Membaca seluruh tabel padahal hanya beberapa kolom yang dihitung. Seluruh chunk ikut terbaca dan query menjadi lambat. Perbaikannya adalah selalu mencantumkan parameter
columnspada pembacaan yang terarah. - Memilih kolom partisi dengan nilai yang sangat bervariasi, misalnya id transaksi. Hampir setiap baris mendapat direktorinya sendiri sehingga jumlah berkas meledak. Perbaikannya adalah mempartisi pada kolom dengan nilai terbatas seperti tanggal atau wilayah.
Ringkasan#
- Parquet menyimpan data per kolom dalam row group dan page, dengan footer yang menyimpan schema dan statistik untuk pembacaan selektif.
- Column pruning dan predicate pushdown bekerja karena tata letak tersebut, sehingga query hanya membaca bagian berkas yang benar-benar diperlukan.
- Partisi menambah tingkat pemangkasan di atas berkas, dengan granularitas yang disesuaikan dengan pola filter query.
Referensi#
- Apache Parquet documentation — Apache Parquet — Dokumentasi resmi format Parquet, termasuk tata letak berkas dan encodingnya.
- pyarrow.parquet.read_table — Apache Arrow — Referensi parameter columns untuk pemangkasan kolom saat membaca.
- Tabular Datasets — Apache Arrow — Panduan pyarrow.dataset untuk menulis dan membaca dataset terpartisi.