Lambda architecture pada chapter sebelumnya menyelesaikan pertukaran kesegaran dan keakuratan, tetapi memindahkannya ke kompleksitas: satu definisi bisnis hidup di dua codebase. Jay Kreps, salah satu pencipta Apache Kafka, mengajukan penyederhanaan yang sekarang dikenal sebagai Kappa architecture: singkirkan jalur batch, simpan log event cukup lama, dan lakukan reprocessing dengan memutar ulang log dari awal lewat versi job baru. Satu codebase, satu jalur, dan log yang sama berfungsi ganda sebagai sumber real-time sekaligus arsip. Bab ini mensimulasikan mekanisme tersebut dan menunjukkan syarat yang membuatnya sah: log harus bisa di-replay.

Satu Jalur: Log dan Replay #

Inti Kappa adalah keyakinan bahwa log append-only sudah cukup sebagai satu-satunya sumber kebenaran. Job streaming membaca log secara kontinu dan menyimpan hasilnya ke serving table; ketika logika pemrosesan perlu diubah, kita tidak menyentuh job lama. Sebagai gantinya, versi baru job membaca ulang log dari offset 0 ke tabel output baru, pembaca berpindah setelah hasilnya siap, dan job lama dihentikan.

Simulasi Replay dengan DuckDB #

Kita mainkan peran Kafka topic dengan tabel DuckDB bernama event_log yang memiliki kolom log_offset monoton. Enam event pertama sudah berada di dalam log, dan dua tabel output disiapkan berdampingan:

python
import duckdb

con = duckdb.connect()

# --- Log append-only: hanya INSERT, tidak ada UPDATE/DELETE ---
con.execute("""
    CREATE TABLE event_log (
        log_offset INTEGER, event_id VARCHAR, kota VARCHAR,
        status VARCHAR, nilai BIGINT
    )
""")
con.executemany("INSERT INTO event_log VALUES (?, ?, ?, ?, ?)", [
    (1, "e-201", "jakarta",  "lunas", 150000),
    (2, "e-202", "bandung",  "lunas",  90000),
    (3, "e-203", "jakarta",  "batal",  60000),
    (4, "e-204", "surabaya", "lunas", 120000),
    (5, "e-205", "bandung",  "lunas",  75000),
    (6, "e-206", "jakarta",  "lunas", 200000),
])
con.execute("CREATE TABLE view_v1 (kota VARCHAR, pendapatan BIGINT, jumlah_event BIGINT)")
con.execute("CREATE TABLE view_v2 (kota VARCHAR, pendapatan BIGINT, jumlah_event BIGINT)")

Satu-satunya fungsi pemrosesan melayani kedua versi logika lewat parameter — perhatikan bahwa log tidak pernah diubah:

python
def jalankan_job(tabel_view, hanya_lunas, dari_offset):
    """Proses event offset > dari_offset, lalu gabungkan ke tabel view."""
    filter_status = "AND status = 'lunas'" if hanya_lunas else ""
    con.execute(f"""
        INSERT INTO {tabel_view}
        SELECT kota, sum(nilai), count(*)
        FROM event_log
        WHERE log_offset > {dari_offset} {filter_status}
        GROUP BY kota
    """)
    con.execute(f"""
        CREATE OR REPLACE TABLE {tabel_view} AS
        SELECT kota, sum(pendapatan) AS pendapatan,
               sum(jumlah_event) AS jumlah_event
        FROM {tabel_view} GROUP BY kota
    """)
    ujung = con.execute("SELECT max(log_offset) FROM event_log").fetchone()[0]
    print(f"   job memproses offset {dari_offset + 1}-{ujung}")


def tampil(tabel_view):
    return con.execute(
        f"SELECT * FROM {tabel_view} ORDER BY kota").fetchall()

Skenarionya: job v1 menghitung semua status sebagai pendapatan — bug yang belum disadari. Setelah bug diketahui, job v2 me-replay seluruh log dengan logika yang hanya menerima status lunas, lalu dua event baru (offset 7-8) tiba dan diproses kedua job. Baris pemanggilan skenario dipangkas dari tampilan; hasil lengkapnya:

Output yang Dihasilkan:

text
== Job v1: logika lama (semua status dihitung sebagai pendapatan) ==
   job memproses offset 1-6
   bandung   2 event  Rp165,000
   jakarta   3 event  Rp410,000
   surabaya  1 event  Rp120,000
   -> jakarta termasuk e-203 berstatus batal (Rp60.000): salah tapi belum diketahui

== Bug ditemukan: pesanan batal ikut terhitung sebagai pendapatan ==
Job v2 me-replay log dari offset 0 dengan logika baru (hanya lunas);
view_v1 tetap melayani pembaca selama replay berlangsung
   job memproses offset 1-6
   bandung   2 event  Rp165,000
   jakarta   2 event  Rp350,000
   surabaya  1 event  Rp120,000

== Event baru offset 7-8 tiba; kedua job memproses rentang yang sama ==
   job memproses offset 7-8
   job memproses offset 7-8

== Perbandingan akhir ==
kota        v1 (keliru)  v2 (benar)
bandung       250,000    165,000
jakarta       410,000    350,000
surabaya      215,000    215,000
selisih = nilai event batal: 145000

pembaca berpindah ke view_v2; view_v1 dihapus
log tetap utuh, hanya pernah INSERT: 8 event, offset 1-8, tidak ada baris yang diubah

Mari kita telaah baris-baris kode di atas:

  1. Blok pertama, baris 6-19 (event_log): log append-only dengan log_offset yang selalu meningkat; seluruh skenario hanya memakai INSERT, tidak ada UPDATE atau DELETE atas baris lama.
  2. Blok pertama, baris 20-21 (view_v1 dan view_v2): dua tabel output hidup berdampingan; pembaca lama tetap dilayani view_v1 selama view_v2 dibangun oleh replay.
  3. Blok kedua, baris 3 (filter_status): perbaikan logika berupa satu parameter — log tidak berubah, yang berubah hanyalah cara job membacanya.
  4. Blok kedua, baris 4-16 (jalankan_job): job memproses event dengan log_offset > dari_offset lalu menggabungkan hasilnya ke tabel view; replay hanyalah pemanggilan yang sama dengan dari_offset=0, seperti terlihat pada baris output job memproses offset 1-6 yang muncul dua kali.

Perbandingan akhir pada output menunjukkan selisih Rp145.000 — tepat sebesar nilai event batal yang ikut terhitung di v1. Setelah pembaca berpindah, view_v1 dihapus dan log tetap utuh.

Syarat Replay: Retensi dan Compaction #

Kappa hanya sah bila log benar-benar bisa diputar ulang. Kafka menyediakan dua mekanisme yang perlu dipertimbangkan: retensi berbasis waktu atau ukuran (log dipertahankan beberapa hari sampai bertahun-tahun, dengan biaya storage yang mengikuti), dan log compaction (untuk topic berbasis kunci, hanya nilai terbaru per kunci yang dipertahankan).

Lambda atau Kappa #

AspekLambda ArchitectureKappa Architecture
Jalur pemrosesanbatch + speedsatu jalur stream
Reprocessingbatch layer recomputereplay log dengan job versi baru
Jumlah codebasedua, wajib setarasatu
Prasyarat utamadata tersedia utuh untuk batchlog retensi panjang dan bisa di-replay
Cocok saatevent telat banyak, recompute murahlogika sering berubah, log terkelola

Dokumentasi resmi mekanisme log, retensi, dan compaction yang menjadi fondasi Kappa tersedia di kafka.apache.org .

Sejauh ini seluruh arsitektur kita bergerak pada satu sumbu: kecepatan dan kebenaran pemrosesan. Pertanyaan berikutnya berpindah ke dimensi lain — organisasi. Ketika puluhan domain bisnis dan banyak tim terlibat, siapa yang sebaiknya memiliki datanya? Chapter Data Mesh menjawab dengan membalik arsitektur terpusat.