Chapter Batch Processing vs Stream Processing di awal course sudah memetakan perbedaan konseptual kedua gaya; section ini mengambil sudut yang lebih mekanis. Pertanyaannya bukan lagi “kapan hasil dibutuhkan”, melainkan “apa yang berbeda di dalam kode” ketika agregasi yang sama dijalankan dengan dua gaya. Pemicunya nyata: tim marketing TokoKita meminta dashboard kampanye menampilkan total pendapatan per kota tanpa menunggu rekap pukul 06:00. Sebelum memutuskan, kita jalankan kedua gaya atas data yang persis sama, ukur waktunya, lalu verifikasi hasilnya identik.
Dua Gaya, Satu Hasil yang Sama #
Gaya batch bekerja dalam dua fase: fase tunggu — data dikumpulkan sampai dianggap lengkap — lalu fase proses — agregasi dijalankan sekali jalan atas seluruh kumpulan. Gaya stream melebur dua fase tersebut: setiap event yang tiba langsung memperbarui hasil, sehingga hasil antara selalu tersedia dan tidak pernah ada momen “menunggu data lengkap”. Kedua gaya menghasilkan angka akhir yang sama; yang berbeda adalah kapan angka tersebut mulai bisa dibaca.
| Aspek | Gaya batch | Gaya stream |
|---|---|---|
| Fase kerja | tunggu lengkap, proses sekali jalan | proses tiap event begitu tiba |
| Hasil antara | tidak ada — tabel hasil muncul di akhir | snapshot terbaca kapan saja |
| Hasil pertama siap | setelah seluruh data terkumpul | setelah event pertama |
| Pemulihan kegagalan | jalankan ulang job atas data sama | lanjut dari posisi baca terakhir |
| Bentuk operasional | job terjadwal (DAG Airflow, job Spark) | service yang berjalan terus |
Satu hal yang tidak berubah di antara kedua gaya: definisi hasil akhirnya. Total pendapatan per kota dihitung dengan aturan yang sama — jumlahkan nilai seluruh pesanan lalu kelompokkan per kota. Gaya hanya menentukan kapan dan berapa kali perhitungan diperbarui; selama definisi bisnisnya tetap, hasil akhir kedua gaya wajib identik. Prinsip inilah yang membuat perpindahan gaya bisa diverifikasi otomatis: jalankan keduanya atas data yang sama, lalu bandingkan hasilnya.
Eksperimen Terukur dengan Python #
Eksperimen berikut mengagregasi 200.000 pesanan TokoKita menjadi total pendapatan per kota. Generator aliran_pesanan memainkan peran kedatangan event satu per satu; time.perf_counter mengukur setiap fase.
"""Agregasi pendapatan per kota, dua gaya: batch (kumpulkan semua lalu proses)
vs stream (hasil diperbarui tiap event). Diverifikasi hasilnya identik."""
import random
import time
KOTA = ["jakarta", "bandung", "surabaya", "medan", "denpasar"]
JUMLAH_EVENT = 200_000
def aliran_pesanan():
"""Generator: event pesanan 'tiba' satu per satu sepanjang hari."""
acak = random.Random(7)
for _ in range(JUMLAH_EVENT):
yield acak.choice(KOTA), acak.randrange(25, 750) * 1000
def gaya_batch():
"""Batch: menunggu seluruh data terkumpul, baru agregasi sekali jalan."""
mulai = time.perf_counter()
data = list(aliran_pesanan()) # fase tunggu: data dikumpulkan dulu
selesai_kumpul = time.perf_counter()
total = {}
for kota, nilai in data: # fase proses: baru dikerjakan setelah lengkap
jumlah, sum_nilai = total.get(kota, (0, 0))
total[kota] = (jumlah + 1, sum_nilai + nilai)
selesai_proses = time.perf_counter()
print("GAYA BATCH")
print(f" menunggu data lengkap : {selesai_kumpul - mulai:.3f} detik")
print(f" agregasi setelah lengkap : {selesai_proses - selesai_kumpul:.3f} detik")
print(f" hasil pertama tersedia setelah {selesai_proses - mulai:.3f} detik\n")
return total
def gaya_stream():
"""Stream: hasil diperbarui tiap event, siap dibaca kapan saja."""
total = {}
mulai = time.perf_counter()
for nomor, (kota, nilai) in enumerate(aliran_pesanan(), start=1):
jumlah, sum_nilai = total.get(kota, (0, 0))
total[kota] = (jumlah + 1, sum_nilai + nilai)
if nomor in (1, JUMLAH_EVENT // 2):
print(f" [stream] setelah {nomor:>7,} event: "
f"hasil sementara sudah terbaca (t={time.perf_counter() - mulai:.3f}s)")
print(f" [stream] seluruh {JUMLAH_EVENT:,} event terproses "
f"(t={time.perf_counter() - mulai:.3f}s)\n")
return total
def main():
print(f"Agregasi pendapatan {JUMLAH_EVENT:,} pesanan TokoKita per kota:\n")
hasil_batch = gaya_batch()
print("GAYA STREAM")
hasil_stream = gaya_stream()
print("Verifikasi hasil batch == hasil stream:", hasil_batch == hasil_stream)
for kota in sorted(hasil_batch):
jumlah, sum_nilai = hasil_batch[kota]
print(f" {kota:<9} {jumlah:>6,} pesanan Rp{sum_nilai:>13,}")
if __name__ == "__main__":
main()Output yang Dihasilkan:
Agregasi pendapatan 200,000 pesanan TokoKita per kota:
GAYA BATCH
menunggu data lengkap : 0.241 detik
agregasi setelah lengkap : 0.037 detik
hasil pertama tersedia setelah 0.278 detik
GAYA STREAM
[stream] setelah 1 event: hasil sementara sudah terbaca (t=0.000s)
[stream] setelah 100,000 event: hasil sementara sudah terbaca (t=0.162s)
[stream] seluruh 200,000 event terproses (t=0.296s)
Verifikasi hasil batch == hasil stream: True
bandung 39,949 pesanan Rp15,501,596,000
denpasar 39,837 pesanan Rp15,417,790,000
jakarta 40,079 pesanan Rp15,453,523,000
medan 40,172 pesanan Rp15,590,759,000
surabaya 39,963 pesanan Rp15,497,344,000Mari kita telaah baris-baris kode di atas:
- Baris 11-15 (
aliran_pesanan): generator memainkan aliran event; benihRandom(7)membuat data eksperimen identik untuk kedua gaya. - Baris 21 (
data = list(...)): inilah fase tunggu batch — generator dipaksa habis menjadi list; data “lengkap” baru ada setelah baris tersebut selesai. - Baris 25-27 (loop agregasi batch): agregasi baru dikerjakan setelah seluruh data tersimpan di memori, ciri khas olahan sekali jalan.
- Baris 41-43 (loop gaya stream): agregat per kota diperbarui di dalam loop tanpa list penampung — tidak ada momen menunggu data lengkap.
- Baris 44-46 (checkpoint): cetakan di tengah jalan membuktikan hasil antara sudah terbaca setelah event pertama dan setelah 100.000 event.
- Baris 59 (verifikasi): kedua dictionary dibandingkan —
Trueberarti pindah gaya tidak mengubah angka akhirnya.
Membaca Hasil Eksperimen #
Gaya batch menunjukkan profil dua fase yang jelas: 0,241 detik menunggu data lengkap, lalu 0,037 detik mengagregasi. Hasil pertama baru bisa dibaca setelah 0,278 detik — pada pipeline produksi, “menunggu lengkap” bukan sepersekian detik melainkan jendela hingga pukul 06:00. Gaya stream mencetak hasil sementara pada event pertama (t=0.000s), dan angka tersebut terus diperbarui sampai seluruh 200.000 event terproses pada t=0.296s.
Perhatikan bahwa stream tidak lebih cepat secara total — kedua gaya menyelesaikan seluruh data dalam rentang 0,2-0,3 detik yang kurang lebih sama, dan angka pastinya bergeser antar eksekusi (pada run kedua, fase tunggu batch terukur 0,168 detik). Nilai stream bukan throughput melainkan kesegaran: hasil antara tersedia sejak detik pertama, bukan hanya di akhir. Satu baris Verifikasi ... True menutup kekhawatiran yang paling sering menghentikan migrasi: mengubah gaya pengolahan tidak mengubah angka akhirnya.
Memilih Gaya untuk Kebutuhan yang Benar #
Tiga pertanyaan berikut memandu keputusan di TokoKita:
- Apakah nilai hasil meluruh seiring waktu? Deteksi penipuan dan sisa stok kehilangan nilai dalam menit — layak stream; rekap komisi penjual tetap cukup batch.
- Apakah hasil antara sudah bermakna? Total pendapatan berjalan bermakna sejak event pertama; rata-rata yang butuh seluruh populasi tidak.
- Apakah organisasi siap mengoperasikan service 24 jam? Stream berarti monitoring, pemulihan state, dan penanganan event telat — beban yang tidak ada pada job terjadwal.
Kedua gaya tersebut sama-sama membutuhkan satu hal yang belum kita bahas: jalur bagi event untuk berpindah dari aplikasi ke pipeline secara andal. Pola arsitektur yang menjawab kebutuhan tersebut kita bangun pada Chapter Event-Driven Architecture.