Windowing #

Ketika kita menganalisis aliran data real-time, salah satu pertanyaan paling mendasar yang harus kita jawab adalah: “Berapa banyak peristiwa yang terjadi dalam kurun waktu tertentu?” Kita mungkin ingin menghitung jumlah klik tombol situs web selama 5 menit terakhir, memantau volume transfer bank per jam untuk mendeteksi pencucian uang, atau melacak aktivitas pengguna dalam satu sesi interaksi aktif. Di Apache Kafka Streams, kebutuhan analisis temporal ini difasilitasi oleh fitur Windowing (Penyusunan Jendela Waktu). Windowing memungkinkan kita membagi aliran peristiwa tak terbatas menjadi potongan-potongan segmen waktu yang terkelola secara stateful. Melalui artikel ini, kita akan membedah secara mendalam empat tipe jendela waktu (Tumbling, Hopping, Sliding, dan Session), teknik penanganan data telat (Late-Arriving Data) menggunakan Grace Period, serta cara mengoptimalkan pengeluaran data hasil agregasi menggunakan Suppress API.


Konsep Dasar Windowing dan Peran Waktu #

Dalam pemrosesan aliran data terdistribusi, definisi “waktu” itu sendiri sangatlah kompleks. Sebelum menggunakan Windowing, kita harus memahami tiga jenis konsep waktu yang didukung oleh Kafka Streams:

  • Event Time (Waktu Peristiwa): Waktu ketika peristiwa tersebut terjadi di dunia nyata atau di perangkat klien. Waktu ini dicatat langsung di dalam metadata record (timestamp) oleh produser saat data dikirimkan. Ini adalah jenis waktu paling penting dan paling akurat untuk agregasi windowing.
  • Ingestion Time (Waktu Penerimaan): Waktu ketika peristiwa tersebut diterima dan ditulis ke dalam log broker Kafka.
  • Processing Time (Waktu Pemrosesan): Waktu lokal ketika utas pemroses di instance aplikasi Kafka Streams mengeksekusi record tersebut. Jenis waktu ini sangat tidak konsisten karena dipengaruhi oleh latensi jaringan atau lag antrean.

Kafka Streams memproses Windowing berdasarkan Event Time secara default. Hal ini menjamin bahwa meskipun terjadi delay jaringan yang sangat lama, data yang datang terlambat akan tetap dikelompokkan ke dalam jendela waktu yang benar berdasarkan waktu pembuatannya yang asli, bukan waktu saat ia sampai di server pemroses.


Empat Tipe Jendela Waktu Utama #

Kafka Streams menyediakan empat model pembagian jendela waktu yang masing-masing dirancang untuk menyelesaikan kebutuhan bisnis yang spesifik:

1. Tumbling Windows (Jendela Waktu Tetap Tanpa Tumpang Tindih) #

Tumbling Windows membagi aliran data menjadi segmen waktu dengan panjang tetap yang saling bersambungan tanpa adanya tumpang tindih (non-overlapping).

  • Karakteristik: Jika kita mendefinisikan Tumbling Window berdurasi 5 menit, maka jendela waktu akan terbentuk dari pukul 00:00 - 00:05, 00:05 - 00:10, 00:10 - 00:15, dan seterusnya. Setiap peristiwa hanya akan masuk ke dalam tepat satu jendela waktu.
  • Use Case: Laporan berkala terstruktur, seperti grafik jumlah penjualan per jam atau total konsumsi bandwidth per 30 menit.

2. Hopping Windows (Jendela Waktu Tetap dengan Tumpang Tindih) #

Hopping Windows memiliki lebar waktu tetap, namun bergeser (hop) ke depan berdasarkan durasi interval langkah yang lebih kecil dari ukuran jendela tersebut.

  • Karakteristik: Jika kita mendefinisikan Hopping Window dengan durasi 5 menit dan interval geser (advance interval) sebesar 1 menit, kita akan mendapatkan jendela bertumpang tindih: 00:00 - 00:05, 00:01 - 00:06, 00:02 - 00:07, dan seterusnya. Sebuah peristiwa tunggal yang terjadi pada pukul 00:03 akan masuk ke dalam 4 jendela waktu yang berbeda secara paralel.
  • Use Case: Menghitung rata-rata bergerak (moving average), seperti menghitung rata-rata suhu mesin dalam 1 jam terakhir yang diperbarui setiap 5 menit sekali.

3. Sliding Windows (Jendela Waktu Dinamis Berdasarkan Kedatangan Event) #

Sliding Windows tidak sejajar dengan garis waktu jam dinding secara statis. Jendela ini dibuat secara dinamis hanya ketika ada peristiwa baru yang masuk ke dalam sistem.

  • Karakteristik: Jendela waktu ditarik ke belakang sepanjang durasi tertentu (misal 5 menit) terhitung dari timestamp peristiwa baru yang diterima. Jendela waktu hanya mencakup peristiwa-peristiwa yang terjadi dalam batas waktu 5 menit sebelum peristiwa terbaru tersebut.
  • Use Case: Kriteria deteksi fraud, seperti memeriksa apakah pengguna melakukan transaksi lebih dari 10 kali dalam kurun waktu 30 detik terakhir kapan saja.

4. Session Windows (Jendela Waktu Berbasis Periode Inaktivitas) #

Session Windows digunakan untuk mengelompokkan peristiwa-peristiwa yang terjadi secara rapat berdasarkan aktivitas pengguna individu.

  • Karakteristik: Jendela ini tidak memiliki ukuran waktu tetap. Ia ditentukan oleh batas periode inaktivitas (inactivity gap). Jika pengguna terus melakukan klik dengan interval kurang dari 15 menit, semua klik tersebut akan digabungkan ke dalam satu Session Window yang sama. Begitu pengguna berhenti melakukan klik selama lebih dari 15 menit, jendela tersebut akan ditutup. Klik berikutnya dari pengguna tersebut akan memulai Session Window yang baru.
  • Use Case: Analisis perilaku pengguna web, pelacakan durasi sesi bermain game, atau sesi belanja pelanggan di aplikasi seluler.
TIAP WINDOWS DALAM DETAIL:
-------------------------------------------------------------------
Tumbling (Size: 5s):
  [0s-5s]   [5s-10s]  [10s-15s]

Hopping (Size: 5s, Advance: 2s):
  [0s-5s]
    [2s-7s]
      [4s-9s]

Session (Inactivity Gap: 5s):
  ●──●──● (Event) ──── [10s Gap] ──── ●──● (Event)
  [==== Session 1 ====]               [== Session 2 ==]
-------------------------------------------------------------------

Penanganan Data Telat: Grace Period dan Late Arrivals #

Dalam sistem terdistribusi, data telat (late arrivals) adalah keniscayaan akibat koneksi internet seluler yang sering terputus-putus. Misalnya, sebuah perangkat sensor IoT mengirim data suhu pukul 08:00. Namun, karena kehilangan sinyal di terowongan, data tersebut baru sampai di broker Kafka kita pada pukul 08:15.

Secara default, jika kita tidak mengonfigurasi batas toleransi, Kafka Streams akan memproses record tersebut. Namun, jika state store untuk jendela pukul 08:00 sudah dihapus dari RocksDB lokal karena dianggap sudah terlalu lama berlalu, data telat tersebut akan dibuang secara senyap.

Solusi: Grace Period #

Untuk mengatasi masalah ini, kita harus menentukan konfigurasi grace() secara eksplisit saat mendefinisikan windowing.

  • Cara Kerja: Grace Period menentukan berapa lama jendela waktu yang sudah terlampaui diizinkan tetap terbuka di memori RocksDB untuk menampung data yang datang terlambat.
  • Siklus Hidup: Jika kita membuat Tumbling Window berdurasi 5 menit dengan Grace Period selama 2 menit, maka jendela waktu pukul 08:00 - 08:05 akan tetap dibuka di RocksDB lokal hingga waktu aliran data sistem (Stream Time) mencapai pukul 08:07. Setiap data telat bertimestamp antara 08:00 - 08:05 yang tiba sebelum pukul 08:07 akan tetap dihitung dan dimasukkan ke dalam agregasi window tersebut. Setelah melewati pukul 08:07, jendela tersebut akan ditutup secara permanen (closed), dan data telat berikutnya untuk jendela tersebut akan dibuang.

Suppress API: Mengurangi Semburan Update Sementara #

Secara default, setiap kali ada record baru masuk ke dalam jendela aktif, Kafka Streams akan langsung memancarkan hasil agregasi terbaru ke topik output (downstream). Jika kita menghitung transaksi per jam, dan ada 1.000 transaksi dalam satu jam untuk satu user, KStreams akan memancarkan 1.000 record update ke topik output.

Hal ini memicu masalah performa parah (database write amplification) jika sistem hilir kita berupa database relasional atau API pihak ketiga yang lambat.

Solusi: Suppress API #

Untuk menekan semburan update sementara (intermediate updates) ini, kita menggunakan operator suppress().

  • Cara Kerja: Suppress memerintahkan Kafka Streams untuk menahan semua update sementara di dalam buffer memori internal.
  • Hasil Akhir: KStreams hanya akan memancarkan satu record tunggal (hasil kalkulasi final) ke downstream tepat pada saat jendela waktu tersebut ditutup secara permanen (setelah melewati durasi window + Grace Period).

Kode Penerapan: Anti-Pattern vs Solusi Windowing Stateful #

Mari kita pelajari implementasi windowing menggunakan Java SDK, membandingkan pendekatan anti-pattern dengan solusi terkelola yang aman.

Kasus Penggunaan #

Kita ingin memantau klik pengguna pada halaman e-commerce (page-click-events) dan menghitung total klik per pengguna setiap 5 menit sekali. Kita ingin mengakomodasi data telat hingga 1 menit, dan hanya mengirimkan hasil akhir perhitungan ke topik keluaran final-click-counts.

Anti-Pattern: Melakukan Windowing Manual Menggunakan Java Thread / Timer #

Mencoba memotong aliran waktu secara manual di memori menggunakan thread java scheduler (ScheduledExecutorService) atau loop timer adalah anti-pattern yang merusak konsistensi data saat crash.

// ANTI-PATTERN: Melakukan kalkulasi windowing secara manual menggunakan Timer / Scheduled Thread Java.
// ✗ Tidak terintegrasi dengan Event Time, rawan data loss saat restart, dan tidak mendukung Grace Period terdistribusi.
public class ManualWindowProcessor {
    private static final Map<String, Integer> clickCounts = new ConcurrentHashMap<>();

    public static void processStream(KStream<String, String> stream) {
        // ✗ BAHAYA: Pemotongan waktu berbasis sistem jam dinding lokal (Processing Time)
        ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
        executor.scheduleAtFixedRate(() -> {
            // Pancarkan hasil berkala ke database eksternal secara tidak aman
            clickCounts.forEach((userId, count) -> {
                log.info("User {} clicked {} times in the last interval", userId, count);
            });
            clickCounts.clear(); // Hapus cache manual (rentan balapan thread!)
        }, 0, 5, TimeUnit.MINUTES);

        stream.foreach((userId, event) -> {
            clickCounts.merge(userId, 1, Integer::sum);
        });
    }
}

Solusi Praktis: Agregasi dengan Tumbling Window dan Suppress API #

Di bawah ini adalah kode Java SDK yang benar dan direkomendasikan. Kita mengonfigurasi Tumbling Window berdurasi 5 menit, menetapkan Grace Period selama 1 menit, dan menerapkan Suppress API untuk memastikan hanya hasil akhir yang dikirimkan.

// BENAR: Menggunakan KGroupedStream, TimeWindows dengan Grace Period, dan Suppress API.
// ✓ Menjamin akurasi berbasis Event Time, aman dari kegagalan crash, dan sangat hemat bandwidth.
public class ResilientWindowAggregation {
    
    public static void buildTopology(StreamsBuilder builder) {
        KStream<String, String> clickStream = builder.stream(
            "page-click-events",
            Consumed.with(Serdes.String(), Serdes.String())
        );

        // Langkah 1: Kelompokkan data berdasarkan User ID (Key)
        KGroupedStream<String, String> groupedClicks = clickStream.groupByKey(
            Grouped.with(Serdes.String(), Serdes.String())
        );

        // Langkah 2: Definisikan Tumbling Window berdurasi 5 menit dengan Grace Period 1 menit
        // ✓ Memaksa parser mempertahankan status window di RocksDB lokal
        TimeWindows tumblingWindow = TimeWindows.ofSizeWithNoGrace(Duration.ofMinutes(5))
            .grace(Duration.ofMinutes(1)); // Toleransi data telat hingga 1 menit

        // Langkah 3: Agregasikan data ke dalam jendela waktu secara stateful
        KTable<Windowed<String>, Long> windowedCounts = groupedClicks
            .windowedBy(tumblingWindow)
            .count(
                Materialized.<String, Long, WindowStore<Bytes, byte[]>>as("click-count-store")
                    .withKeySerde(Serdes.String())
                    .withValueSerde(Serdes.Long())
            );

        // Langkah 4: Terapkan Suppress API untuk menahan update sementara
        // ✓ Hanya memancarkan record final ke topik tujuan ketika window ditutup (setelah menit ke-6)
        windowedCounts
            .suppress(Suppressed.untilWindowCloses(
                Suppressed.BufferConfig.maxRecords(10000) // Batas buffer memori pengaman
                    .shutDownWhenFull() // Keamanan jika buffer meledak
            ))
            .toStream()
            // Mengubah kunci Windowed<String> menjadi String biasa untuk kemudahan pembacaan hilir
            .selectKey((windowedKey, count) -> String.format("%s@%d-%d", 
                windowedKey.key(), 
                windowedKey.window().start(), 
                windowedKey.window().end()
            ))
            .to(
                "final-click-counts",
                Produced.with(Serdes.String(), Serdes.Long())
            );
    }
}

Pembersihan State Store Window yang Kedaluwarsa #

State Store window di RocksDB tidak boleh membengkak selamanya. Untuk mencegah kehabisan disk space di server lokal, Kafka Streams secara otomatis mengelola pembersihan data window yang sudah lewat secara bertahap.

  • Retention Time (Waktu Retensi): Secara default, waktu retensi state store window adalah durasi window ditambah Grace Period dikalikan dua ((Window Size + Grace Period) * 2).
  • Pembagian Segmen Fisik: Di bawah kap mesin, RocksDB membagi database persisten menjadi beberapa file segmen fisik terpisah berdasarkan waktu. Ketika sebuah segmen terisi penuh oleh data jendela waktu yang telah melewati masa retensi tersebut, segmen RocksDB yang memuat jendela waktu terkait akan dihapus secara permanen sekaligus dari disk lokal.
  • Penghapusan berbasis segmen fisik ini jauh lebih efisien daripada menghapus baris satu per satu secara logis, karena tidak memicu fragmentasi data di RocksDB. Data telat yang tiba setelah waktu retensi habis tidak akan diproses dan dibuang.

Ringkasan #

  • Event Time — Parameter waktu utama yang digunakan di Windowing Kafka Streams untuk mengelompokkan record berdasarkan waktu terjadinya peristiwa di sisi klien.
  • Tumbling Windows — Jendela waktu dengan durasi tetap yang saling bersambungan secara linier tanpa adanya tumpang tindih (non-overlapping).
  • Hopping Windows — Jendela waktu dengan durasi tetap yang maju ke depan dengan interval pergeseran lebih kecil, memicu terjadinya tumpang tindih (overlapping).
  • Session Windows — Jendela waktu dinamis tanpa ukuran tetap yang ditentukan oleh periode inaktivitas data pengguna (inactivity gap).
  • Grace Period — Toleransi waktu tunggu tambahan untuk membiarkan jendela waktu yang sudah terlampaui tetap terbuka di RocksDB untuk menampung data telat.
  • Suppress API — Operator pemotong semburan record yang menahan update sementara di memori dan hanya memancarkan hasil akhir setelah jendela ditutup secara permanen.
  • Retention Time — Batas waktu penahanan data window di disk lokal RocksDB sebelum dihapus permanen untuk mencegah kehabisan ruang penyimpanan.

← Sebelumnya: Fault Tolerance   Berikutnya: Join Stream and Table →

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact