Laporan penjualan harian TokoKita hampir selesai ketika tim finance menemukan selisih: satu pesanan pukul 23:59:41 tidak masuk laporan tanggal 15 Januari, padahal pelanggan jelas memesan sebelum tengah malam. Pesanan tersebut tidak hilang — pesanan itu masuk laporan 16 Januari, karena pipeline memprosesnya pukul 00:01:12. Inilah perbedaan event time (waktu kejadian menurut pelanggan) dan processing time (waktu kejadian menurut pipeline), dan keduanya hampir tidak pernah sama. Chapter ini membedah kedua jam tersebut, mensimulasikan bagaimana keterlambatan jaringan membuat pesanan tiba teracak, lalu menunjukkan mengapa windowing berbasis waktu proses menghasilkan laporan yang salah.
Dua Jam yang Berbeda #
Event time adalah waktu saat kejadian benar-benar terjadi, dicatat oleh perangkat atau layanan pembuat event, dan menumpang sebagai field di payload. Processing time adalah waktu saat prosesor streaming menerima pesan tersebut — dicatat oleh jam prosesor, tidak pernah dikirim oleh siapa pun. Sebuah pesanan dan pemrosesannya terpisah oleh perjalanan pesan: jaringan, antrean, retry, dan beban sistem.
{
"id_pesanan": 1208,
"waktu_event": "2026-01-15T23:59:41+07:00",
"total": 434000,
"pelanggan": "[email protected]"
}| Aspek | Event time | Processing time |
|---|---|---|
| Sumber | Jam pembuat event | Jam prosesor saat pesan diterima |
| Lokasi | Field di payload | Tidak dikirim; dicatat saat pemrosesan |
| Urutan kedatangan | Bisa teracak | Selalu naik secara monoton |
| Terpengaruh keterlambatan | Tidak | Ya — delay menambah nilainya |
Dari Mana Keterlambatan Datang #
Selisih event time dan processing time bukan kejadian langka; selisih tersebut adalah keadaan normal yang harus diasumsikan selalu terjadi. Sumbernya berlapis:
- Jaringan dan retry — paket hilang, koneksi ponsel berganti sel, timeout memaksa pengiriman ulang.
- Perangkat offline — aplikasi mobile menyimpan pesanan secara lokal lalu mengirimnya begitu sinyal kembali; keterlambatan bisa berjam-jam.
- Backpressure — consumer yang kewalahan membuat pesan menunggu lebih lama di partisi.
- Jeda pemeliharaan — garbage collection, patch sistem, dan restart layanan menambah selisih dalam hitungan detik sampai menit.
Simulasi: Pesanan yang Tiba Teracak #
Simulasi berikut membuat 15 pesanan TokoKita, satu pesanan setiap 4 detik, masing-masing tertahan delay jaringan acak 0,5-6 detik. Pesanan ke-8 berasal dari ponsel yang sempat offline dan terkirim 42,7 detik kemudian. Prosesor menerima pesanan berurutan sesuai kedatangan lalu menstempel waktu terimanya.
"""Simulasi event time vs processing time pada pesanan TokoKita.
15 pesanan dibuat satu per 4 detik, masing-masing tertahan delay jaringan
acak 0,5-6 detik. Pesanan ke-8 berasal dari ponsel yang sempat offline
sehingga tertahan 42,7 detik. Prosesor mencatat waktu penerimaan
(processing time) dan membandingkannya dengan waktu pesanan dibuat (event time).
"""
import random
from datetime import datetime, timedelta
MULAI = datetime(2026, 1, 15, 10, 0, 0)
JUMLAH = 15
LEBAR_WINDOW = 30 # detik
rng = random.Random(2026)
pesanan = []
for i in range(JUMLAH):
pesanan.append({
"id": i + 1,
"event": MULAI + timedelta(seconds=i * 4),
"delay": rng.uniform(0.5, 6.0),
"total": rng.randint(50, 500) * 1000, # Rp50rb - Rp500rb
})
pesanan[7]["delay"] = 42.7 # ponsel offline: pesanan ikut terkirim saat online
# urutan kedatangan = urutan waktu diterima prosesor
pesanan.sort(key=lambda p: p["event"] + timedelta(seconds=p["delay"]))
def jam(dt):
return dt.strftime("%H:%M:%S")
def window_ke(dt):
return int((dt - MULAI).total_seconds() // LEBAR_WINDOW)
print("Urutan kedatangan di prosesor:")
print(f"{'No':>3} {'Waktu event':<10}{'Delay':>8} {'Waktu diterima':<15}{'Urutan'}")
maks_event = None
acak = 0
for p in pesanan:
diterima = p["event"] + timedelta(seconds=p["delay"])
if maks_event is not None and p["event"] < maks_event:
acak += 1
urutan = "lebih tua dari sebelumnya"
else:
urutan = "sesuai"
maks_event = max(maks_event or p["event"], p["event"])
print(f"{p['id']:>3} {jam(p['event']):<10}{p['delay']:>6.1f}s "
f"{jam(diterima)}.{int(diterima.microsecond / 100000):<9} {urutan}")
delay = [p["delay"] for p in pesanan]
print(f"\nLag event->terima: min {min(delay):.1f}s, rata-rata "
f"{sum(delay) / len(delay):.1f}s, maks {max(delay):.1f}s")
print(f"Pesanan tiba di luar urutan event time: {acak} dari {JUMLAH}\n")
print(f"Agregasi per window {LEBAR_WINDOW} detik (jumlah pesanan, GMV):")
print(f"{'Window':<24}{'Berdasar waktu proses':<28}{'Berdasar waktu event'}")
for w in range(3):
mulai = MULAI + timedelta(seconds=w * LEBAR_WINDOW)
akhir = mulai + timedelta(seconds=LEBAR_WINDOW)
by_proses = [p for p in pesanan
if w == window_ke(p["event"] + timedelta(seconds=p["delay"]))]
by_event = [p for p in pesanan if w == window_ke(p["event"])]
def gmv(ps):
rupiah = f"{sum(x['total'] for x in ps):,}".replace(",", ".")
return f"{len(ps)} pesanan, Rp {rupiah}"
print(f"[{jam(mulai)} - {jam(akhir)}) {gmv(by_proses):<28}{gmv(by_event)}")
terlambat = next(p for p in pesanan if p["id"] == 8)
diterima = terlambat["event"] + timedelta(seconds=terlambat["delay"])
print(f"\nPesanan #8 (ponsel offline) dibuat pukul {jam(terlambat['event'])} "
f"namun diterima {jam(diterima)}: waktu proses menaruhnya di window "
f"[{jam(MULAI + timedelta(seconds=window_ke(diterima) * LEBAR_WINDOW))} - ...)")Output yang Dihasilkan:
Urutan kedatangan di prosesor:
No Waktu event Delay Waktu diterima Urutan
1 10:00:00 1.2s 10:00:01.1 sesuai
2 10:00:04 5.7s 10:00:09.7 sesuai
3 10:00:08 5.2s 10:00:13.2 sesuai
4 10:00:12 5.4s 10:00:17.3 sesuai
5 10:00:16 3.9s 10:00:19.9 sesuai
6 10:00:20 4.8s 10:00:24.8 sesuai
7 10:00:24 5.1s 10:00:29.1 sesuai
9 10:00:32 4.8s 10:00:36.7 sesuai
10 10:00:36 1.8s 10:00:37.8 sesuai
11 10:00:40 0.9s 10:00:40.9 sesuai
13 10:00:48 0.6s 10:00:48.5 sesuai
12 10:00:44 5.0s 10:00:48.9 lebih tua dari sebelumnya
14 10:00:52 3.2s 10:00:55.1 sesuai
15 10:00:56 1.7s 10:00:57.6 sesuai
8 10:00:28 42.7s 10:01:10.7 lebih tua dari sebelumnya
Lag event->terima: min 0.6s, rata-rata 6.1s, maks 42.7s
Pesanan tiba di luar urutan event time: 2 dari 15
Agregasi per window 30 detik (jumlah pesanan, GMV):
Window Berdasar waktu proses Berdasar waktu event
[10:00:00 - 10:00:30) 7 pesanan, Rp 2.189.000 8 pesanan, Rp 2.623.000
[10:00:30 - 10:01:00) 7 pesanan, Rp 1.904.000 7 pesanan, Rp 1.904.000
[10:01:00 - 10:01:30) 1 pesanan, Rp 434.000 0 pesanan, Rp 0
Pesanan #8 (ponsel offline) dibuat pukul 10:00:28 namun diterima 10:01:10: waktu proses menaruhnya di window [10:01:00 - ...)Mari kita telaah baris-baris kode di atas:
- Baris 19-26 (pembuatan pesanan): setiap pesanan membawa
event,delay, dantotal; baris 26 menimpa delay pesanan ke-8 menjadi 42,7 detik untuk memodelkan ponsel offline. - Baris 29 (urutan kedatangan): sorting berdasar
event + delay— inilah urutan yang dilihat prosesor, dan urutan tersebut tidak sama dengan urutan event time. - Baris 46-48 (deteksi urutan): pesanan yang event time-nya lebih tua dari pesanan mana pun yang sudah tiba ditandai; hasilnya 2 dari 15, termasuk pesanan #12 yang hanya tergeser satu posisi.
- Baris 62-67 (dua skema bucket):
by_prosesmengelompokkan menurut waktu terima,by_eventmenurut waktu kejadian — baris yang sama, dua jam yang berbeda. - Baris 69-73 (GMV per window): hasil kedua skema dicetak berdampingan sehingga selisihnya langsung terbaca.
- Baris 75-79 (baris penutup): kasus pesanan #8 diringkas satu kalimat — dibuat 10:00:28, dihitung pada window 10:01:00.
Window yang Salah, Laporan yang Salah #
Tabel agregasi di output adalah bukti kerusakannya. Window pertama seharusnya memuat 8 pesanan bernilai Rp 2.623.000, tetapi jam proses hanya melihat 7 pesanan bernilai Rp 2.189.000 — selisih Rp 434.000 itu adalah pesanan #8 yang “hilang” dari window-nya sendiri. Pada saat yang sama, window ketiga memuat 1 pesanan yang sebenarnya tidak ada kejadian apa pun di rentang tersebut: pesanan pukul 10:00:28 dihitung sebagai aktivitas pukul 10:01. Untuk laporan penjualan, kesalahan ini memindahkan pendapatan antarperiode; untuk deteksi penipuan, kesalahan ini merusak perhitungan frekuensi per menit; untuk pengukuran SLA, kesalahan ini menyesatkan seluruh kesimpulan.
Windowing berbasis processing time tetap berguna untuk pertanyaan tentang sistem itu sendiri — berapa pesan yang sanggup diproses per menit, seberapa dalam antrean menumpuk. Tetapi untuk pertanyaan tentang dunia yang direpresentasikan data — berapa penjualan per menit — satu-satunya jam yang sah adalah event time.
Konsekuensi untuk Desain Pipeline #
Perbedaan kedua jam ini menuntut tiga kebiasaan desain. Pertama, event time wajib dibawa di payload sejak event pertama dibuat; menambahkannya belakangan berarti menulis ulang sejarah. Kedua, bucketing dan agregasi selalu memakai event time, sementara processing time dipakai untuk metrik kesehatan pipeline. Ketiga, pipeline harus punya kebijakan untuk data yang terlambat — berapa lama kita bersedia menunggu sebelum sebuah window dinyatakan selesai.
Kebijakan menunggu data terlambat inilah yang memunculkan konsep window sebagai unit agregasi stream. Tiga bentuk window — tumbling, sliding, dan session — beserta cara menggabungkan event ke dalamnya kita praktikkan pada Chapter Windowing.