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:

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

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

text
== 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,000

Mari kita telaah baris-baris kode di atas:

  1. Blok pertama, baris 12-17 (tambah_event): simulasi kedatangan event — INSERT dengan id berurut dan nilai acak dari generator berbenih 42 agar hasil dapat direproduksi.
  2. Blok pertama, baris 20-24 (recompute_penuh): gaya batch — setiap pemanggilan membaca seluruh log dan mengagregasi ulang; tidak ada state tersimpan.
  3. Blok pertama, baris 27-35 (gabung_delta): gaya incremental — hanya baris id >= mulai_id yang dikerjakan, lalu hasilnya digabungkan ke state yang dibawa lintas pemanggilan.
  4. 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 #

AspekBatch recomputeMikro-batchStreaming penuh
Interval pemrosesanjam-an hingga harianmenit-ankontinu
Baris dikerjakan per runseluruh logdelta sejak run laluper event
Kompleksitas staterendahsedangtinggi
Usia data rata-ratatinggisedangrendah
Cocok untukmodel warehouse, backfilldashboard operasionaldeteksi, 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.