Lambda memisahkan batch dan stream ke dua codebase; Kappa menyatukannya di satu jalur stream. Praktik industri modern bergerak ke arah yang lebih tenang: batch dan streaming bukan dua arsitektur yang bersaing, melainkan dua gaya komputasi di atas fondasi yang sama — log append-only, tabel lakehouse, dan satu codebase transformasi. Mesin seperti Spark Structured Streaming bahkan memperlakukan batch sebagai stream yang berhenti di satu titik. Bab ini membuktikan idenya lewat simulasi: satu agregasi dirawat dua cara, recompute penuh dan incremental, dan keduanya wajib menghasilkan angka identik.
Dua Gaya Komputasi, Satu Fondasi #
Bayangkan satu tabel lakehouse yang hanya menerima append, persis seperti log Kafka pada chapter Kappa Architecture. Gaya batch membaca seluruh tabel dan mengagregasi ulang setiap kali dijadwalkan berjalan — sederhana, mudah diaudit, dan tahan bug karena tidak ada state yang bisa basi. Gaya streaming atau incremental hanya mengerjakan baris baru lalu menggabungkannya ke state yang sudah ada — hemat, tetapi menuntut penanda posisi (checkpoint atau watermark) yang benar.
Kedua gaya tersebut adalah ujung-ujung sebuah spektrum, bukan dua dunia terpisah. Konsep yang sama hidup di level warehouse sebagai model incremental pada chapter Incremental Models, dan di level ingestion sebagai Incremental Load pada section Pengumpulan dan Ingestion Data.
Recompute versus Incremental #
Kita ukur perbedaan keduanya secara langsung. Sebuah log menerima 100.000 event pada hari pertama dan 30.000 event tambahan pada hari kedua; kedua gaya merawat agregat count dan sum per kota, dan hasilnya dibandingkan setiap hari:
import random
import duckdb
con = duckdb.connect()
acak = random.Random(42)
con.execute("CREATE TABLE event_log (id INTEGER, kota VARCHAR, nilai BIGINT)")
KOTA = ("jakarta", "bandung", "surabaya", "medan")
def tambah_event(jumlah, mulai_id):
"""Simulasikan event baru yang tiba: INSERT ke log dengan id berurut."""
baris = [(mulai_id + i, acak.choice(KOTA), acak.randrange(10, 500) * 1000)
for i in range(jumlah)]
con.executemany("INSERT INTO event_log VALUES (?, ?, ?)", baris)
return mulai_id + jumlah
def recompute_penuh():
"""Gaya batch: baca dan agregasi ulang seluruh log."""
return {kota: (jml, rp) for kota, jml, rp in con.execute(
"SELECT kota, count(*), sum(nilai) FROM event_log GROUP BY kota"
).fetchall()}
def gabung_delta(state, mulai_id):
"""Gaya incremental: hanya event id >= mulai_id yang dikerjakan."""
for kota, jml, rp in con.execute(f"""
SELECT kota, count(*), sum(nilai) FROM event_log
WHERE id >= {mulai_id} GROUP BY kota
""").fetchall():
lama = state.get(kota, (0, 0))
state[kota] = (lama[0] + jml, lama[1] + rp)
return state
def baris_log():
return con.execute("SELECT count(*) FROM event_log").fetchone()[0]Dua hari simulasi kemudian berjalan bergantian untuk kedua gaya:
state_incremental = {}
# --- Hari 1: 100.000 event pertama ---
id_berikut = tambah_event(100_000, mulai_id=1)
hasil_batch = recompute_penuh()
state_incremental = gabung_delta(state_incremental, mulai_id=1)
print("== Hari 1: 100.000 event baru ==")
print(f" batch : mengerjakan ulang {baris_log():,} baris (seluruh log)")
print(f" incremental: mengerjakan 100.000 baris (semuanya baru)")
print(" hasil identik:", hasil_batch == state_incremental)
# --- Hari 2: 30.000 event tambahan ---
mulai_delta = id_berikut
id_berikut = tambah_event(30_000, mulai_id=id_berikut)
hasil_batch = recompute_penuh()
state_incremental = gabung_delta(state_incremental, mulai_id=mulai_delta)
print("\n== Hari 2: 30.000 event baru (total log 130.000 baris) ==")
print(f" batch : mengerjakan ulang {baris_log():,} baris (seluruh log)")
print(f" incremental: mengerjakan 30.000 baris (hanya delta)")
print(" hasil identik:", hasil_batch == state_incremental)Output yang Dihasilkan:
== Hari 1: 100.000 event baru ==
batch : mengerjakan ulang 100,000 baris (seluruh log)
incremental: mengerjakan 100.000 baris (semuanya baru)
hasil identik: True
== Hari 2: 30.000 event baru (total log 130.000 baris) ==
batch : mengerjakan ulang 130,000 baris (seluruh log)
incremental: mengerjakan 30.000 baris (hanya delta)
hasil identik: True
== Hasil akhir kedua gaya (identik) ==
bandung 32,575 event Rp8,260,414,000
jakarta 32,436 event Rp8,249,220,000
medan 32,653 event Rp8,293,653,000
surabaya 32,336 event Rp8,236,773,000
total 130,000 event Rp33,040,060,000Mari kita telaah baris-baris kode di atas:
- Blok pertama, baris 12-17 (
tambah_event): simulasi kedatangan event —INSERTdenganidberurut dan nilai acak dari generator berbenih 42 agar hasil dapat direproduksi. - Blok pertama, baris 20-24 (
recompute_penuh): gaya batch — setiap pemanggilan membaca seluruh log dan mengagregasi ulang; tidak ada state tersimpan. - Blok pertama, baris 27-35 (
gabung_delta): gaya incremental — hanya barisid >= mulai_idyang dikerjakan, lalu hasilnya digabungkan kestateyang dibawa lintas pemanggilan. - Blok kedua, baris 12-20 (hari 2): perhatikan
mulai_delta = id_berikut— incremental hanya mengerjakan 30.000 baris baru sementara batch mengulang 130.000 baris, dan kedua hasil tetap identik.
Pada hari pertama kedua gaya mengerjakan jumlah baris yang sama karena semuanya baru — incremental baru menunjukkan keunggulan sejak hari kedua, dan jurang tersebut melebar setiap hari seiring log bertambah.
Mikro-batch: Spektrum di Tengah #
| Aspek | Batch recompute | Mikro-batch | Streaming penuh |
|---|---|---|---|
| Interval pemrosesan | jam-an hingga harian | menit-an | kontinu |
| Baris dikerjakan per run | seluruh log | delta sejak run lalu | per event |
| Kompleksitas state | rendah | sedang | tinggi |
| Usia data rata-rata | tinggi | sedang | rendah |
| Cocok untuk | model warehouse, backfill | dashboard operasional | deteksi, personalisasi |
Mikro-batch adalah kompromi paling populer: interval dipendekkan sampai beberapa menit sehingga state tetap sederhana, tetapi data sudah cukup segar untuk dashboard operasional. Spark Structured Streaming menjalankan model persis ini di atas satu engine yang sama untuk batch dan stream — dokumentasinya ada di spark.apache.org , sementara detail log pendukungnya di kafka.apache.org .
Dengan seluruh pola kini berada di atas satu meja — layered, medallion, lambda, kappa, mesh, fabric, DDD, hingga unified batch-stream — pertanyaan yang tersisa bersifat praktis: memilih yang mana dengan proses yang bisa dipertanggungjawabkan? Chapter Memilih Arsitektur Data menyiapkan kerangkanya.