Agregasi biasa seperti GROUP BY menyembunyikan satu pertanyaan: kapan perhitungannya boleh dianggap selesai? Pada batch, jawabannya jelas — seluruh tabel sudah lengkap di tangan. Pada stream, data mengalir tanpa henti, sehingga “total penjualan per menit” tidak akan pernah terhitung kalau kita menunggu data berhenti. Windowing memecah stream tak berujung menjadi potongan berhingga berdasarkan waktu, dan bentuk potongannya menentukan pertanyaan apa yang bisa dijawab. Chapter ini mempraktikkan tiga bentuk window utama — tumbling, sliding, dan session — dengan windower Python murni di atas stream klik produk TokoKita.
Mengapa Stream Butuh Window #
Window adalah himpunan bagian dari stream yang dibatasi waktu: sekumpulan event yang waktu kejadiannya jatuh pada rentang tertentu. Begitu batas waktu sebuah window terlewati, window ditutup, agregasinya dihitung sekali, dan hasilnya diteruskan ke hilir — pipeline kembali menunggu window berikutnya. Dengan cara ini, stream tak berujung diolah menjadi rangkaian hasil berujung: satu baris agregat per window, siap dimuat ke tabel atau dashboard.
Batas window dibaca dari event time — jam kejadian di payload, bukan jam penerimaan — sebagaimana dibuktikan pada Chapter Event Time vs Processing Time. Keputusan yang tersisa hanyalah memilih bentuk potongannya.
Tiga Bentuk Window #
Tumbling Window #
Tumbling window memotong waktu menjadi segmen berurutan dengan lebar tetap, tanpa tumpang tindih: menit pertama, menit kedua, menit ketiga. Setiap event masuk tepat satu window. Bentuk ini cocok untuk laporan periodik — total penjualan per menit, jumlah pesanan per jam.
Sliding Window #
Sliding window memakai lebar tetap tetapi bergeser sejauh langkah yang lebih kecil dari lebarnya, sehingga window bertumpang tindih: window 90 detik yang bergeser tiap 30 detik menempatkan setiap event pada tiga window sekaligus. Tumpang tindih inilah yang menghasilkan tren halus seperti moving average, karena setiap angka menggabungkan masa lalu yang berbeda-beda.
Session Window #
Session window tidak memakai batas waktu tetap. Window terbentuk dari aktivitas itu sendiri: event yang berdekatan (jeda di bawah ambang tertentu) digabung menjadi satu sesi, dan sesi berakhir ketika jeda melewati ambang tersebut. Hasilnya window dengan panjang bervariasi yang mengikuti perilaku pengguna — cocok untuk mengukur kunjungan belanja, bukan detik kalender.
Simulasi: Windower Murni Python #
Simulasi berikut membuat 15 klik produk dengan jeda acak 5-34 detik, lalu membaginya dengan ketiga bentuk window: tumbling 60 detik, sliding 60 detik bergeser 30 detik, dan session dengan ambang jeda 20 detik.
"""Windower murni Python: tumbling, sliding, dan session window.
Stream: 15 klik produk TokoKita, jeda antar klik acak 5-34 detik.
Semua waktu dalam detik sejak 09:00:00.
"""
import random
from datetime import datetime, timedelta
MULAI = datetime(2026, 1, 15, 9, 0, 0)
LEBAR = 60 # detik: lebar tumbling dan sliding window
LANGKAH = 30 # detik: pergeseran sliding window
GAP_SESI = 20 # detik: jeda maksimum agar klik dianggap satu sesi
rng = random.Random(117)
klik = []
t = 0.0
for i in range(15):
t += rng.uniform(5, 34)
klik.append((t, f"user-{i % 3}"))
def jam(detik):
return (MULAI + timedelta(seconds=detik)).strftime("%H:%M:%S")
print(f"15 klik antara {jam(klik[0][0])} dan {jam(klik[-1][0])} "
f"(span {klik[-1][0]:.0f} detik)\n")
print(f"Tumbling window: lebar {LEBAR} dtk, tanpa tumpang tindih")
for w in range(int(klik[-1][0] // LEBAR) + 1):
isi = [c for c in klik if w * LEBAR <= c[0] < (w + 1) * LEBAR]
pengguna = ", ".join(sorted({u for _, u in isi}))
print(f" [{jam(w * LEBAR)} - {jam((w + 1) * LEBAR)}) "
f"{len(isi)} klik ({pengguna})")
print(f"\nSliding window: lebar {LEBAR} dtk, bergeser {LANGKAH} dtk")
mulai = 0.0
while mulai < klik[-1][0]:
isi = [c for c in klik if mulai <= c[0] < mulai + LEBAR]
if isi:
print(f" [{jam(mulai)} - {jam(mulai + LEBAR)}) {len(isi)} klik")
mulai += LANGKAH
print(f"\nSession window: klik dengan jeda <= {GAP_SESI} dtk digabung")
sesi, awal, akhir = [], klik[0][0], klik[0][0]
for waktu, _ in klik[1:]:
if waktu - akhir > GAP_SESI:
sesi.append((awal, akhir))
awal = waktu
akhir = waktu
sesi.append((awal, akhir))
for i, (a, b) in enumerate(sesi, 1):
isi = [c for c in klik if a <= c[0] <= b]
pengguna = ", ".join(sorted({u for _, u in isi}))
print(f" sesi {i}: [{jam(a)} - {jam(b)}] {len(isi)} klik, "
f"durasi {b - a:.0f} dtk ({pengguna})")Output yang Dihasilkan:
15 klik antara 09:00:11 dan 09:04:01 (span 241 detik)
Tumbling window: lebar 60 dtk, tanpa tumpang tindih
[09:00:00 - 09:01:00) 4 klik (user-0, user-1, user-2)
[09:01:00 - 09:02:00) 4 klik (user-0, user-1, user-2)
[09:02:00 - 09:03:00) 3 klik (user-0, user-1, user-2)
[09:03:00 - 09:04:00) 3 klik (user-0, user-1, user-2)
[09:04:00 - 09:05:00) 1 klik (user-2)
Sliding window: lebar 60 dtk, bergeser 30 dtk
[09:00:00 - 09:01:00) 4 klik
[09:00:30 - 09:01:30) 4 klik
[09:01:00 - 09:02:00) 4 klik
[09:01:30 - 09:02:30) 3 klik
[09:02:00 - 09:03:00) 3 klik
[09:02:30 - 09:03:30) 3 klik
[09:03:00 - 09:04:00) 3 klik
[09:03:30 - 09:04:30) 3 klik
[09:04:00 - 09:05:00) 1 klik
Session window: klik dengan jeda <= 20 dtk digabung
sesi 1: [09:00:11 - 09:01:13] 6 klik, durasi 61 dtk (user-0, user-1, user-2)
sesi 2: [09:01:33 - 09:01:33] 1 klik, durasi 0 dtk (user-0)
sesi 3: [09:01:54 - 09:02:09] 2 klik, durasi 15 dtk (user-1, user-2)
sesi 4: [09:02:38 - 09:02:44] 2 klik, durasi 6 dtk (user-0, user-1)
sesi 5: [09:03:17 - 09:04:01] 4 klik, durasi 44 dtk (user-0, user-1, user-2)Mari kita telaah baris-baris kode di atas:
- Baris 19-21 (stream klik): jeda antar klik diambil dari rentang 5-34 detik — cukup rapat untuk membentuk sesi panjang, cukup renggang untuk memecahnya.
- Baris 32-36 (tumbling): nomor window
wmenentukan batas tetapw*60sampai(w+1)*60; setiap klik masuk tepat satu window, terlihat dari total 4+4+3+3+1 = 15. - Baris 39-44 (sliding): titik awal window bergeser 30 detik per langkah; karena lebar 60 detik dibagi langkah 30 detik menghasilkan dua, setiap klik paling banyak dihitung di dua window.
- Baris 47-53 (penggabungan sesi): selama jeda ke klik berikutnya masih di bawah ambang,
akhirterus maju dan sesi memanjang; begitu jeda melewati ambang, sesi ditutup dan sesi baru dibuka pada klik tersebut. - Baris 54-58 (cetak sesi): setiap sesi menampilkan rentang, jumlah klik, dan durasinya — durasi 61 detik untuk 6 klik versus 0 detik untuk 1 klik.
Perhatikan karakter ketiga tipe tersebut pada output. Tumbling menghasilkan lima window dengan panjang waktu sama namun isi berbeda — laporan per menit yang rapi. Sliding menghasilkan sembilan hasil yang bertumpang tindih — klik yang sama muncul dua kali, memberi deret angka yang bergeser halus. Session menghasilkan lima sesi dengan durasi 61, 0, 15, 6, dan 44 detik — bentuknya mengikuti aktivitas, bukan kalender; sesi pertama yang padat (6 klik dalam 61 detik) adalah sinyal minat belanja yang tidak akan terlihat pada window 60 detik.
Memilih Window untuk Kasus #
| Tipe window | Batas waktu | Tumpang tindih | Pertanyaan yang dijawab |
|---|---|---|---|
| Tumbling | Tetap, berurutan | Tidak | Berapa total penjualan per menit |
| Sliding | Tetap, bergeser rutin | Ya | Bagaimana tren rata-rata 90 detik terakhir |
| Session | Mengikuti aktivitas | Tidak | Berapa lama satu kunjungan belanja |
Satu pertanyaan praktis belum terjawab di semua bentuk window: kapan sebuah window boleh ditutup? Menutup terlalu cepat membuat pesanan ponsel offline tertinggal; menunggu terlalu lama membuat dashboard basi. Kebijakan penutupan window inilah materi Chapter Watermarks dan Late Events.