Data lake adalah tempat menyimpan data sebagai file di object storage (S3, GCS, Cloudflare R2, Azure Blob), dalam format apa pun dan dalam bentuk aslinya, supaya murah disimpan banyak dan bisa dibaca banyak mesin. Bayangin gudang ekspedisi. Paket masuk disimpan apa adanya di rak yang diberi label tanggal: murah, muat apa saja, dan gak perlu dibongkar dulu. Toko di depan (warehouse) cuma memajang barang yang sudah dirapikan dan diberi harga. Gudang yang raknya gak berlabel dan gak ada yang mencatat isinya lama-lama jadi tumpukan yang gak ada yang berani sentuh.
🧪 Tebakan cepat: penjualan bersih Toko Buku disimpan sebagai 11 file Parquet, satu folder per hari (
dt=2026-09-28/sampaidt=2026-10-09/). QueryWHERE dt = '2026-10-01'membuat DuckDB membuka berapa file? DanWHERE kota = 'Bandung'? Tebak dulu, jawabannya ada di bagian "Partisi: satu folder per hari".
Lake, warehouse, lakehouse
| Data warehouse | Data lake | Lakehouse | |
|---|---|---|---|
| Isinya | tabel yang sudah bersih | file apa saja: CSV, JSON, Parquet, log, gambar | file Parquet + metadata tabel |
| Schema | dicek saat data ditulis (schema-on-write) | diterapkan saat data dibaca (schema-on-read) | dicek saat ditulis, bisa berubah tercatat |
| Tempat | BigQuery, Snowflake, Redshift, file DuckDB | S3, GCS, R2 | S3, GCS, R2 |
| Dibaca dengan | mesin warehouse itu | DuckDB, Spark, Trino, pandas | mesin mana pun yang paham format tabelnya |
Perbedaan utamanya: lake menyimpan data mentah dalam format aslinya dan schema baru diterapkan waktu dibaca, warehouse menyimpan data terstruktur yang schema-nya dicek waktu masuk. Lakehouse menambahkan fitur warehouse (transaksi, perubahan schema, versi lama) di atas file lake.
Lapisan bronze, silver, gold
Lake biasanya dibagi tiga lapisan (medallion architecture). Ini lake Toko Buku dari satu run DuckDB 1.5.6 di laptop, dengan ukuran file aslinya:
lake/
├── bronze/order_lines/ mentah, CSV, persis seperti ekstrak
│ ├── dt=2026-09-28/data_0.csv 291 byte, 3 baris
│ ├── dt=2026-10-01/data_0.csv 274 byte, 3 baris (item 9 masih ada)
│ └── ... (11 folder, 28 September sampai 9 Oktober)
├── silver/penjualan/ bersih, Parquet
│ ├── dt=2026-09-28/data_0.parquet 1.137 byte, 3 baris
│ ├── dt=2026-10-01/data_0.parquet 1.083 byte, 2 baris
│ └── ... (11 folder juga)
└── gold/juara_mingguan.parquet 790 byte, jawaban pemilik (9 baris)
- Bronze (raw): salinan sumber apa adanya, gak pernah diubah. Kalau aturan bersih salah, silver bisa dibangun ulang dari sini.
- Silver (cleaned): baris dobel dibuang, kota
NULLjadiTidak diketahui, harga dari katalog. 18 baris, 21 buku, Rp 2.214.000. - Gold (aggregated): tabel siap pakai untuk satu kebutuhan, di sini juara per kota per minggu. Dashboard membaca gold.
Partisi: satu folder per hari
Partisi artinya data dipecah ke folder menurut nilai satu kolom. Nama foldernya kolom=nilai (gaya Hive), jadi mesin query tahu isi folder tanpa membukanya. Script yang membangun lake di atas:
import os
import duckdb
for lapisan in ["bronze", "silver", "gold"]:
os.makedirs(f"lake/{lapisan}", exist_ok=True)
con = duckdb.connect()
# Bronze: salinan mentah apa adanya, CSV, satu folder per hari
con.sql("""
COPY (SELECT *, CAST(created_at AS DATE) AS dt FROM 'order_lines.csv')
TO 'lake/bronze/order_lines' (FORMAT csv, HEADER, PARTITION_BY (dt), OVERWRITE)
""")
# Silver: sudah dibersihkan, Parquet, tetap satu folder per hari
con.sql("""
COPY (
SELECT r.order_id, r.book_id, r.created_at,
COALESCE(r.city, 'Tidak diketahui') AS kota,
r.title AS judul, r.qty, b.catalog_price AS harga, r.dt
FROM read_csv('lake/bronze/order_lines/*/*.csv', hive_partitioning = true) r
JOIN 'books.csv' b ON b.book_id = r.book_id
QUALIFY ROW_NUMBER() OVER (PARTITION BY r.order_id, r.book_id ORDER BY r.item_id) = 1
)
TO 'lake/silver/penjualan' (FORMAT parquet, PARTITION_BY (dt), OVERWRITE)
""")
# Gold: query juara per kota per minggu dari silver, COPY ke
# lake/gold/juara_mingguan.parquet (dipendekkan)
PARTITION_BY (dt)membuat satu folderdt=...per tanggal. Kolomdtgak ditulis di dalam file; nilainya dibaca dari nama folder.hive_partitioning = truemengembalikandtjadi kolom waktu dibaca.QUALIFYmenyaring hasil window function langsung (ada di DuckDB, BigQuery, Snowflake), pengganti CTE plusWHERE ke = 1dari pelajaran SQL.OVERWRITEdibutuhkan untuk menjalankan ulang: tanpa itu DuckDB menolak denganDirectory "lake/bronze/order_lines" is not empty! Enable OVERWRITE option to overwrite files. DuckDB juga gak membuat folder induknya, jadios.makedirsduluan. Di S3, ganti path-nya jadis3://....
Lalu query ke silver dengan filter tanggal:
print(con.sql("""
SELECT dt, kota, judul, qty, harga
FROM read_parquet('lake/silver/penjualan/*/*.parquet', hive_partitioning = true)
WHERE dt = '2026-10-01'
"""))
Baris hasilnya, lalu potongan EXPLAIN ANALYZE untuk query yang sama:
│ 2026-10-01 │ Jakarta │ Bumi Manusia │ 2 │ 125000 │
│ 2026-10-01 │ Bandung │ Bumi Manusia │ 1 │ 125000 │
│ File Filters: │
│ (dt = '2026-10-01'::DATE) │
│ │
│ Scanning Files: 1/11 │
│ Total Files Read: 1 │
DuckDB membuka 1 dari 11 file; 10 folder lain dilewati hanya dari namanya. Itu jawaban tebakannya. WHERE dt >= '2026-10-05' membuka 5 dari 11. WHERE kota = 'Bandung' membuka 11 dari 11: kota bukan kolom partisi, jadi semua file harus dibaca dulu baru disaring (hasilnya 5 baris). Pilih kolom partisi dari filter yang paling sering dipakai, biasanya tanggal.
Open table format: Iceberg, Delta Lake, Hudi
Folder Parquet biasa gak punya transaksi. Kalau COPY ... OVERWRITE mati di tengah, sebagian folder sudah baru dan sebagian masih lama, dan query yang jalan saat itu membaca campurannya. Open table format menambahkan file metadata di samping file Parquet yang mencatat file mana saja yang menyusun versi tabel saat ini. Tiga yang dipakai luas (versi terbaru per Oktober 2026):
| Format | Versi | Asal |
|---|---|---|
| Apache Iceberg | 1.12.0 (30 September 2026) | Netflix, sekarang proyek Apache |
| Delta Lake | 4.4.1 (5 Oktober 2026) | Databricks, sekarang Linux Foundation |
| Apache Hudi | 1.2.1 (24 September 2026) | Uber, sekarang proyek Apache |
Yang ditambahkan ketiganya:
- Transaksi ACID. Satu tulis berhasil semua atau gak sama sekali; pembaca selalu melihat versi yang utuh. Ini juga yang membuat
MERGE,UPDATE, danDELETEper baris bisa dilakukan di lake, misalnya menerapkan event CDC atau menghapus data satu pelanggan. - Schema evolution. Menambah atau mengganti nama kolom tercatat di metadata, tanpa menulis ulang semua file.
- Time travel. Versi lama tetap tercatat, jadi bisa query "isi tabel kemarin" atau membatalkan tulis yang salah.
Untuk membaca dan menulisnya dipakai Spark, Flink, Trino, atau DuckDB (extension iceberg dan delta), plus sebuah catalog yang mencatat tabel apa saja yang ada dan di mana.
Kapan Toko Buku butuh semua ini
Jujur saja: sekarang belum. 19 baris pesanan muat di satu file DuckDB atau satu PostgreSQL replica, dan query-nya selesai dalam milidetik. Silver Toko Buku malah lebih besar dari sumbernya: 11 file Parquet masing-masing sekitar 1,1 KB, hampir 12 KB total, padahal order_lines.csv cuma 1.449 byte. Tiap file Parquet membawa schema dan metadatanya sendiri, dan untuk 1 sampai 3 baris per file, biaya itu yang dominan.
Lake mulai masuk akal kalau datanya sudah gigabyte sampai terabyte, datang dari banyak sumber (log klik, event aplikasi, file dari partner), ada data yang bukan tabel (gambar sampul, PDF), atau beberapa mesin dan tim harus membaca data yang sama. Lakehouse masuk akal kalau di atas itu kamu butuh update, delete, dan versi lama di lake.
⚠️ Jebakan yang sering ketemu
- Data swamp. Lake tanpa catalog, tanpa dokumentasi schema, tanpa pemilik, dan tanpa cek kualitas berubah jadi rawa data: data menumpuk, gak ada yang tahu isinya, dan akhirnya gak dipercaya. Catat tiap dataset di catalog, beri pemilik, pasang cek dari pelajaran kualitas data.
- Partisi terlalu halus. Partisi per jam atau per pelanggan menghasilkan ribuan file kecil, dan membuka file jadi lebih mahal daripada membacanya. Usahakan tiap file puluhan sampai ratusan MB.
- Filter di kolom yang bukan partisi.
WHERE kota = 'Bandung'tetap membuka semua file. Kalau filter yang sering dipakai bukan tanggal, pertimbangkan kolom partisi lain. - Menimpa folder Parquet tanpa table format. Tulis yang gagal di tengah meninggalkan tabel setengah lama setengah baru, dan gak ada versi untuk kembali.
TL;DR: Data lake menyimpan data sebagai file di object storage, mentah dalam format aslinya, dengan schema yang diterapkan saat dibaca (schema-on-read); warehouse menyimpan tabel terstruktur yang dicek saat ditulis (schema-on-write); lakehouse menambahkan fitur warehouse di atas file lake. Lake dibagi bronze (mentah), silver (bersih), gold (siap pakai), dan dipartisi per folder dt=2026-10-01/: filter di kolom partisi membuat DuckDB membuka 1 dari 11 file, filter di kolom lain tetap membuka 11. Iceberg 1.12, Delta Lake 4.4, dan Hudi 1.2 menambahkan transaksi ACID, schema evolution, dan time travel. Lake tanpa catalog, pemilik, dan cek kualitas jadi data swamp. Untuk data sekecil Toko Buku, satu database sudah cukup; lake baru untung di gigabyte, banyak sumber, dan banyak pembaca.