Topik ini membahas dua pola yang menopang pemrosesan streaming, yaitu windowing dan semantik pengiriman (delivery semantics). Windowing adalah pengelompokan kejadian menurut stempel waktunya, sedangkan semantik pengiriman adalah jaminan tentang berapa kali satu kejadian memengaruhi hasil. Keduanya disimulasikan dengan Python murni, mulai dari jendela tetap sampai baca ulang yang idempoten, yakni aman dijalankan berulang tanpa mengubah hasil akhirnya. Setelah membacanya, pilihan jendela dan tingkat jaminan pengiriman dapat ditetapkan dengan sadar, bukan mewarisi default kerangka.
Mengelompokkan kejadian dengan windowing#
Kejadian yang datang satu per satu jarang langsung berguna, sehingga windowing menjadi pola dasar pemrosesan streaming. Bentuknya yang paling sederhana adalah tumbling window, yakni jendela berpanjang tetap yang tidak saling tumpang tindih. Contoh yang lazim adalah rekap suhu tiap sepuluh menit, dan pada pola itu setiap kejadian masuk tepat ke satu jendela, lalu hasilnya dihitung per jendela.
Pengelompokan menuntut keputusan tentang waktu mana yang digunakan. Waktu kejadian (event time) diambil dari stempel waktunya, sedangkan waktu pemrosesan (processing time) diambil dari saat kejadian tiba di pemroses. Keduanya berbeda ketika kejadian datang terlambat karena jaringan atau antrean, dan perbedaan itulah sumber kebingungan yang paling sering muncul. Pemrosesan yang benar mengelompokkan menurut waktu kejadian, kemudian menunggu sedikit lebih lama sebelum jendela dianggap selesai, agar kejadian terlambat tetap masuk ke jendelanya.
Simulasi jendela tetap#
Mekanisme tumbling window dapat dilihat tanpa kerangka streaming apa pun. Skrip berikut mengelompokkan enam kejadian suhu berstempel waktu ke jendela sepuluh menit dengan rumus sederhana, yaitu membulatkan menit ke bawah ke kelipatan sepuluh. Jalankan skripnya, lalu cocokkan setiap kejadian dengan jendelanya.
"""Mengelompokkan kejadian berstempel waktu ke jendela tetap sepuluh menit."""
from datetime import datetime, timedelta
PANJANG_JENDELA = timedelta(minutes=10)
KEJADIAN = [
("2026-03-02T08:02:00", "suhu 24.1"),
("2026-03-02T08:07:00", "suhu 24.4"),
("2026-03-02T08:11:00", "suhu 25.0"),
("2026-03-02T08:19:00", "suhu 24.9"),
("2026-03-02T08:20:00", "suhu 25.3"),
("2026-03-02T08:29:00", "suhu 24.6"),
]
def kunci_jendela(waktu: datetime) -> datetime:
"""Titik awal jendela tetap tempat stempel waktu itu jatuh."""
menit = (waktu.minute // 10) * 10
return waktu.replace(minute=menit, second=0, microsecond=0)
jendela: dict[datetime, int] = {}
for teks, _ in KEJADIAN:
awal = kunci_jendela(datetime.fromisoformat(teks))
jendela[awal] = jendela.get(awal, 0) + 1
for awal in sorted(jendela):
akhir = awal + PANJANG_JENDELA
print(f"{awal:%H:%M}-{akhir:%H:%M} : {jendela[awal]} kejadian")08:00-08:10 : 2 kejadian
08:10-08:20 : 2 kejadian
08:20-08:30 : 2 kejadianEnam kejadian berakhir menjadi tiga jendela, masing-masing berisi dua kejadian. Kejadian pada pukul 08.11 dan 08.19 masuk ke jendela 08.10-08.20 karena jendelanya ditentukan oleh stempel waktunya, bukan oleh urutan kedatangannya. Pada kerangka streaming sungguhan seperti Spark Structured Streaming, pengelompokan yang sama tersedia sebagai fungsi jendela, tetapi perilakunya tetap identik dengan rumus sepuluh baris ini, yaitu menetapkan batas jendela dari waktu kejadian lalu menghitung per batas.
Tiga semantik pengiriman#
Semantik pengiriman adalah jaminan tentang berapa kali satu kejadian memengaruhi hasil akhir, dan tiga tingkatannya perlu dibedakan sejak awal. At-most-once memproses setiap kejadian paling banyak sekali, sehingga tidak ada duplikat, tetapi kejadian boleh hilang ketika pemroses mati sebelum hasilnya tersimpan. At-least-once menjamin setiap kejadian diproses sekurang-kurangnya sekali, sehingga tidak ada yang hilang, tetapi kejadian yang sama boleh diproses lebih dari satu kali ketika posisi baca disimpan ulang setelah hasilnya ditulis. Exactly-once menjamin setiap kejadian memengaruhi hasil akhir tepat satu kali, yaitu gabungan jaminan keduanya.
| Tingkat | Risiko yang ditanggung | Persyaratan tambahan |
|---|---|---|
| At-most-once | kejadian hilang saat pemroses mati | hampir tidak ada |
| At-least-once | kejadian diproses ganda | penyimpanan ulang posisi baca |
| Exactly-once | tidak ada yang hilang dan tidak ada yang ganda | transaksi atau idempotensi pada kedua sisi |
Pada praktiknya, exactly-once hampir selalu dibangun di atas at-least-once dengan idempotensi, yakni memastikan pemrosesan ulang tidak mengubah hasil akhirnya. Idempotensi pada sisi consumer, yaitu program pembaca kejadian, diperoleh dengan memberi setiap kejadian kunci yang stabil, misalnya pengenal uniknya, lalu mencatat kunci yang sudah diproses. Dengan cara itu, kejadian ganda yang dijamin muncul oleh at-least-once diabaikan secara alami, sehingga tingkat pengirimannya naik menjadi exactly-once tanpa menuntut infrastruktur transaksi yang rumit.
Simulasi baca ulang yang idempoten#
Hubungan at-least-once dan idempotensi dapat dibuktikan dengan skrip kecil. Fungsi pada skrip berikut memproses kejadian pada satu jendela, tetapi kejadian yang kuncinya sudah tercatat langsung dilewati. Jalankan skripnya dua putaran, sebagaimana pemroses yang membaca ulang jendela yang sama setelah dimulai ulang.
"""Baca ulang jendela yang sama dengan idempotensi berbasis kunci kejadian."""
KEJADIAN_JENDELA = [
("k-1", "2026-03-02T08:02:00", "login ali"),
("k-2", "2026-03-02T08:07:00", "login budi"),
("k-3", "2026-03-02T08:09:00", "catatan_dibuat ali"),
]
def proses_jendela(kunci_sudah_diproses: set[str]) -> int:
"""Proses kejadian pada satu jendela, lewati kunci yang sudah ada."""
masuk_baru = 0
for kunci, _, _ in KEJADIAN_JENDELA:
if kunci in kunci_sudah_diproses:
continue
kunci_sudah_diproses.add(kunci)
masuk_baru += 1
return masuk_baru
diproses: set[str] = set()
print("putaran pertama:", proses_jendela(diproses), "kejadian baru masuk")
print("putaran kedua:", proses_jendela(diproses), "kejadian baru masuk")
print("total kejadian tersimpan:", len(diproses))putaran pertama: 3 kejadian baru masuk
putaran kedua: 0 kejadian baru masuk
total kejadian tersimpan: 3Putaran pertama memasukkan tiga kejadian baru, sedangkan putaran kedua yang membaca jendela yang sama tidak menambah apa pun, sehingga total yang tersimpan tetap tiga. Skema inilah yang bekerja ketika pemroses dimulai ulang, karena baca ulang dari offset lama memang menghasilkan kejadian ganda pada jalur pengirimannya, tetapi kunci kejadian menyaringnya sebelum mengubah hasil akhir. Setelah pola ini tertanam, pilihan tingkat semantik berhenti menjadi tebakan dan menjadi keputusan konfigurasi yang jelas.
Kesalahan umum#
- Mengelompokkan menurut waktu pemrosesan. Penyebabnya, waktu tiba dianggap sama dengan waktu terjadinya kejadian. Akibatnya, kejadian terlambat masuk ke jendela yang salah dan rekap per jendela tidak dapat dipercaya. Perbaikannya, kelompokkan menurut stempel waktu kejadian, lalu beri jeda sebelum jendela ditutup.
- Menganggap exactly-once diperoleh secara default. Penyebabnya, istilahnya sering dibaca sebagai sifat bawaan kerangka streaming. Akibatnya, pemrosesan ulang menggandakan hasil pada target yang tidak idempoten. Perbaikannya, tetapkan kunci kejadian untuk consumer dan sediakan target berkunci unik agar pemrosesan ulang aman.
- Menutup jendela tepat pada batas waktunya. Penyebabnya, jendela dianggap selesai begitu waktunya habis. Akibatnya, kejadian yang datang beberapa detik setelahnya terbuang dari hitungan jendelanya. Perbaikannya, tambahkan jeda kecil setelah batas jendela sebelum hasilnya difinalisasi.
Ringkasan#
- Windowing mengelompokkan kejadian menurut stempel waktunya, dan tumbling window adalah jendela tetap yang tidak tumpang tindih.
- Pengelompokan yang benar menggunakan waktu kejadian, bukan waktu pemrosesan, disertai jeda untuk kejadian yang datang terlambat.
- At-least-once ditambah idempotensi berbasis kunci kejadian menghasilkan efek exactly-once tanpa infrastruktur transaksi tambahan.
Referensi#
- Structured Streaming Programming Guide — Apache Spark — Panduan resmi Spark Structured Streaming tentang windowing, waktu kejadian, dan semantik exactly-once.
- Apache Kafka documentation — Apache Kafka — Penjelasan semantik pengiriman pesan pada producer dan consumer Kafka.