Pipeline yang kita bangun sepanjang course ini hampir selalu bekerja atas berkas dan tabel yang sudah selesai ditulis: ekspor pesanan TokoKita per hari, snapshot stok per jam, hasil query yang berhenti pada satu titik. Aplikasi TokoKita sendiri tidak menghasilkan data secara periodik — aplikasi tersebut melepas event pesanan satu per satu, dua puluh empat jam sehari, tanpa ada yang tahu kapan aliran tersebut berhenti. Stream processing adalah cabang pemrosesan data yang dirancang untuk aliran seperti ini: data diproses begitu tiba, hasil dihitung secara berkelanjutan, dan sistem tidak boleh berasumsi data sudah lengkap. Chapter ini meletakkan tiga fondasi yang dipakai sepanjang section: unbounded data, anatomi sistem stream, dan state yang diperbarui secara increment.
Data Bounded dan Unbounded #
Bounded data adalah kumpulan record yang berawal dan berakhir: berkas ekspor pesanan 15 Januari selesai diunggah dan isinya tidak berubah lagi. Ukurannya diketahui di muka, dan job batch pada section Pemrosesan Data Skala Besar mengolahnya dari baris pertama sampai baris terakhir dalam satu eksekusi. Unbounded data bekerja sebaliknya: jumlah barisnya tidak diketahui, tidak ada baris terakhir, dan setiap saat event baru bisa menyusul dari belakang.
| Aspek | Bounded data | Unbounded data |
|---|---|---|
| Titik akhir | ada — berkas atau tabel selesai ditulis | tidak ada — event terus menyusul |
| Ukuran data | diketahui sebelum diproses | tidak diketahui, tumbuh sepanjang waktu |
| Pola pengolahan | baca semua, proses sekali jalan | proses per event begitu event tiba |
| Bentuk hasil | final setelah job selesai | snapshot yang terus diperbarui |
| Contoh TokoKita | ekspor pesanan harian untuk warehouse | event pesanan langsung dari aplikasi |
Konsekuensi terpenting ada pada baris terakhir tabel: hasil olahan stream tidak pernah final. Angka “total penjualan hari ini” yang dihitung dari stream adalah snapshot — nilai total penjualan sampai detik angka tersebut dihitung, dan angka tersebut akan kedaluwarsa begitu pesanan berikutnya tiba.
Unbounded juga tidak berarti kita harus mengingat seluruh histori. Pertanyaan analitis hampir selalu dibatasi jendela waktu — total penjualan per menit, rata-rata per jam — sehingga state yang perlu dipelihara tetap kecil meski aliran datanya tidak pernah berakhir. Mekanisme jendela tersebut kita bahas tuntas pada Chapter Windowing.
Anatomi Sistem Stream Processing #
Sistem stream processing tersusun dari empat komponen. Source adalah aliran masukan — topic Kafka atau stream perubahan tabel (CDC). Stream processor membaca event satu per satu dan menjalankan logika: memfilter, mengagregasi, atau menggabungkan dengan stream lain. State menyimpan hasil antara yang terus diperbarui, misalnya total penjualan berjalan per kota. Sink adalah tujuan keluaran: dashboard, tabel hasil, sistem peringatan, atau topic lain yang dibaca sistem berikutnya.
State adalah komponen yang paling membedakan stream dari batch. Job batch menyimpan hasil antara di memori selama satu eksekusi lalu membuangnya saat job selesai; stream processor harus menjaga state tetap hidup berminggu-minggu dan mampu memulihkan state setelah kegagalan. Perbedaan waktu kejadian dan waktu pemrosesan juga baru terasa di stream — event pesanan pukul 09:00 bisa diproses pukul 09:02 karena antrean; dampaknya kita bedah tuntas pada Chapter Event Time vs Processing Time.
Simulasi Konsumsi Stream Tak Berujung #
Generator Python adalah model mental yang tepat untuk stream: objek tersebut bersifat lazy, menghasilkan event satu per satu saat diminta, dan tidak pernah berhenti sendiri — persis seperti aliran pesanan TokoKita. Simulasi berikut dikonsumsi secara bertahap sambil memelihara statistik berjalan.
"""Stream tak berujung: generator pesanan + konsumen dengan statistik berjalan."""
import itertools
import random
PELANGGAN = ["budi", "siti", "dewi", "anton", "rina"]
def stream_pesanan():
"""Generator event pesanan TokoKita — tidak pernah berhenti sendiri."""
acak = random.Random(42)
for nomor in itertools.count(start=1):
yield {
"order_id": f"ORD-{nomor:05d}",
"pelanggan": acak.choice(PELANGGAN),
"nilai": acak.randrange(50, 950) * 1000, # Rp50.000 - Rp949.000
}
def main():
stat = {"jumlah": 0, "total": 0, "terbesar": 0}
sumber = stream_pesanan() # satu iterator, dikonsumsi bertahap
print("Konsumsi 8 event pertama dari stream tak berujung:")
for event in itertools.islice(sumber, 8):
stat["jumlah"] += 1
stat["total"] += event["nilai"]
stat["terbesar"] = max(stat["terbesar"], event["nilai"])
rata_rata = stat["total"] / stat["jumlah"]
print(f' {event["order_id"]} {event["pelanggan"]:<5} '
f'Rp{event["nilai"]:>8,} -> jumlah={stat["jumlah"]:>2}, '
f"rata-rata Rp{rata_rata:,.0f}")
print(f"\nSnapshot setelah 8 event : total Rp{stat['total']:,}, "
f"terbesar Rp{stat['terbesar']:,}")
print("Stream belum selesai — data berikutnya sudah tersedia tanpa menunggu.")
# Buktikan stream masih hidup: lanjutkan dari iterator yang sama
print("Event berikutnya tetap mengalir:", next(sumber)["order_id"])
if __name__ == "__main__":
main()Output yang Dihasilkan:
Konsumsi 8 event pertama dari stream tak berujung:
ORD-00001 budi Rp 75,000 -> jumlah= 1, rata-rata Rp75,000
ORD-00002 dewi Rp 300,000 -> jumlah= 2, rata-rata Rp187,500
ORD-00003 siti Rp 192,000 -> jumlah= 3, rata-rata Rp189,000
ORD-00004 budi Rp 742,000 -> jumlah= 4, rata-rata Rp327,250
ORD-00005 rina Rp 139,000 -> jumlah= 5, rata-rata Rp289,600
ORD-00006 rina Rp 482,000 -> jumlah= 6, rata-rata Rp321,667
ORD-00007 budi Rp 80,000 -> jumlah= 7, rata-rata Rp287,143
ORD-00008 budi Rp 273,000 -> jumlah= 8, rata-rata Rp285,375
Snapshot setelah 8 event : total Rp2,283,000, terbesar Rp742,000
Stream belum selesai — data berikutnya sudah tersedia tanpa menunggu.
Event berikutnya tetap mengalir: ORD-00009Mari kita telaah baris-baris kode di atas:
- Baris 9-17 (
stream_pesanan):itertools.count(start=1)tidak pernah habis, sehingga generator tidak pernah menaikkanStopIterationdengan sendirinya — inilah unbounded data dalam bentuk paling murni. - Baris 25 (
itertools.islice(sumber, 8)): konsumen berhenti membaca setelah 8 event karena kebutuhan analisis sudah terpenuhi, bukan karena stream selesai. - Baris 26-28 (pembaruan
stat): jumlah, total, dan nilai terbesar diperbarui per event — tidak ada perhitungan ulang dari awal, karena event yang sudah lewat tidak disimpan. - Baris 29 (
rata_rata = ...): hasil antara dihitung dari state berjalan, bukan dari agregasi seluruh data; inilah snapshot yang selalu siap dibaca. - Baris 39 (
next(sumber)): event kesembilan tersedia seketika tanpa menunggu jendela waktu — sinyal bahwa sumber masih hidup dan terus mengalir.
Tantangan Utama Stream Processing #
Tiga masalah berikut hampir tidak pernah muncul di pipeline batch, tetapi menjadi makanan sehari-hari sistem stream:
- Urutan dan keterlambatan event — event pukul 09:00 bisa tiba setelah event pukul 09:02 karena jalur jaringan berbeda; processor harus punya aturan untuk event telat.
- Laju sumber melebihi laju pengolahan — saat ramai flash sale, event menumpuk lebih cepat daripada kecepatan pemrosesan; antrean di tengah menjadi penyangga dan sumber backpressure.
- Duplikasi dan kegagalan — pengiriman ulang setelah gangguan jaringan membuat event yang sama bisa diproses dua kali; setiap hasil harus tetap benar (idempotent).
Seluruh istilah pada section ini — topic, partition, offset, producer, consumer — berakar pada dokumentasi Apache Kafka , platform streaming yang menjadi rujukan industri. Fondasi konsepnya sudah terpasang; pertanyaan praktis berikutnya adalah apa yang sebenarnya berubah antara pipeline batch yang sudah kita kuasai dan pipeline stream. Perbandingan mekanisnya kita ukur langsung pada Chapter Batch vs Stream Processing.