Kafka Streams adalah pustaka pemrosesan stream yang ditautkan ke aplikasi Java, sehingga logika pemrosesan data berjalan di dalam aplikasi itu sendiri tanpa menambah cluster baru. Topik ini membahas model pustakanya, anatomi processor topology yang menghubungkan sumber, processor, dan sink, perbedaan KStream dengan KTable, serta posisinya di hadapan pemroses eksternal seperti Flink dan Spark Structured Streaming. Satu simulasi Python memutar data melalui topologi yang sama dengan contoh Java-nya, sehingga aliran record terlihat tahap demi tahap. Setelah membacanya, keputusan memilih Kafka Streams atau pemroses eksternal dapat dilandasi model kerja masing-masing.
Pustaka di dalam aplikasi, bukan cluster terpisah#
Kafka Streams berbentuk pustaka (library), yaitu kumpulan kode yang ditautkan ke aplikasi seperti dependency lain, bukan perangkat terpisah yang diinstal. Aplikasi yang menggunakannya berjalan di JVM-nya sendiri, baik pada mesin pengembangan, kontainer, maupun mesin produksi. Aplikasi tersebut membaca topic dari broker, mengolah record-nya, lalu menulis hasilnya ke topic lain, dan bagi broker, aplikasi itu hanyalah consumer sekaligus producer biasa. Broker tidak memiliki komponen pemrosesan khusus dan tidak perlu dikonfigurasi ulang demi menampung aplikasi Streams.
Paralelisme aplikasi mengikuti partisi. Setiap instance aplikasi menjalankan beberapa task, dan setiap task ditetapkan ke satu partisi topic input. Menambah instance membagi task ke anggota baru seperti consumer group, karena koordinasinya memang menggunakan mekanisme consumer group yang sama. Instance yang tiba setelah task terbagi habis berdiri tanpa pekerjaan dan hanya menunggu instance lain berhenti.
Pada consumer biasa, langkah consume-transform-produce, penyimpanan keadaan, dan penulisan ulang hasil harus ditulis manual. Kafka Streams menyediakan DSL transformasi, state lokal, serta penulisan ulang hasil sebagai bagian dari topologi, sehingga ketiga langkah itu menjadi bagian deklarasi pemrosesan. Kode yang berulang antar aplikasi consumer pun menghilang karena pustakanya yang menanganinya.
Topologi processor#
Kafka Streams menyusun pemrosesan dalam processor topology, yaitu graf node processor yang mendeskripsikan alur data dari topic masuk sampai topic keluar. Source processor membaca record dari topic input, processor biasa mentransformasinya, dan sink processor menulis record hasil ke topic output. Topologi dideklarasikan sekali dalam kode, lalu setiap task menjalankan salinan topologi yang sama untuk partisi yang dipegangnya. Bentuk grafnya bebas bercabang dan bergabung, sepanjang alurnya tetap mengikuti struktur yang dideklarasikan.
Pemrosesannya berlangsung record demi record. Sebuah record dibaca oleh sumber, melewati node demi node sesuai urutan graf, lalu ditulis ke sink tanpa menunggu data terkumpul seperti pada pemrosesan batch. Urutan record per partisi tetap terjaga karena satu task mengurus satu partisi dari ujung ke ujung.
Diagram berikut memperlihatkan topologi satu task yang membaca topic suhu-ruang, menyaring bacaan di bawah 30 derajat, lalu menulis sisa recordnya ke topic suhu-panas.
Urutan node pada graf ikut menentukan hasil. Penyaringan sebelum pemformatan membuat processor berikutnya hanya menerima record yang lolos, sedangkan penukaran urutannya membuat record yang dibuang sempat diformat terlebih dahulu. Perbedaan kecil semacam ini makin terasa pada topologi yang menyimpan state, karena setiap record yang lolos ke state menambah beban pemulihannya.
KStream dan KTable#
Dua abstraksi data menjadi titik awal DSL Kafka Streams. KStream menyajikan topic sebagai deretan record, dan setiap record dianggap kejadian baru yang berdiri sendiri. KTable menyajikan topic yang sama sebagai tabel keadaan, yaitu pasangan key dan nilai terkini, karena record baru dengan key yang sama memperbarui entri lamanya. Topic terkompaksi yang membawa perubahan keadaan adalah sumber alami KTable, sesuai pola changelog yang dibahas pada modul Desain topic.
Pilihan antara keduanya mengubah makna hasil pemrosesan. Menghitung kejadian pada KStream pesanan menghasilkan jumlah pesanan per periode, sedangkan menghitung pada KTable stok hanya menghitung produk yang keadaannya berubah. Operasi agregasi pada KStream menghasilkan KTable karena hasilnya adalah keadaan terkini per key, dan mekanisme penyimpanannya dibahas pada topik State store.
Perbedaannya juga tampak pada pembacaan ulang. KStream memutar ulang seluruh kejadian dari awal log, sedangkan KTable membangun ulang keadaannya dari log yang sama karena setiap record berperan sebagai pembaru entri. Oleh karena itu, sumber KTable lazim berupa topic terkompaksi, agar log tidak menumpuk entri keadaan yang sudah digantikan.
Posisi di hadapan Flink dan Spark#
Flink dan Spark Structured Streaming menempatkan pemrosesan pada cluster terpisah. Pekerjaan Flink berjalan di cluster yang terdiri atas JobManager dan TaskManager, sedangkan pekerjaan Spark Structured Streaming berjalan di cluster Spark dengan driver dan executor-nya. Aplikasi dikirim ke cluster sebagai pekerjaan, dan keandalan state ditangani infrastruktur cluster lewat checkpoint ke penyimpanan eksternal. Kafka Streams memindahkan tanggung jawab itu ke aplikasi dan partisi Kafka, tanpa komponen pusat yang dioperasikan sendiri.
Perbandingan berikut merangkum empat aspek, yaitu model penerapan, state, windowing, dan jaminan tepat sekali (exactly-once).
| Aspek | Kafka Streams | Flink | Spark Structured Streaming |
|---|---|---|---|
| Model penerapan | Pustaka di dalam aplikasi JVM, skala dengan jumlah instance | Pekerjaan di cluster terpisah (JobManager, TaskManager) | Pekerjaan di cluster Spark (driver, executor) |
| State | State lokal per task dengan changelog topic | State terdistribusi dengan checkpoint ke storage eksternal | State pada executor dengan checkpoint ke storage eksternal |
| Windowing | Tumbling, hopping, dan session pada DSL | Tumbling, hopping, session, dan window kustom | Tumbling, hopping, dan session pada API DataFrame |
| Exactly-once | Transaksi Kafka dengan pembacaan read_committed | Checkpoint dua fase menuju sink | Checkpoint micro-batch, bergantung dukungan sink |
Ketiga pemroses menyediakan window dan jaminan tepat sekali, sehingga pilihan berada pada model penerapan dan lingkup pekerjaan. Kafka Streams cocok untuk pemrosesan yang sumber dan tujuannya berada di Kafka, karena operasinya cukup berjalan bersama aplikasi tanpa cluster baru. Flink dan Spark lebih tepat ketika pipeline menggabungkan banyak sumber di luar Kafka, state membengkak melebihi kapasitas satu mesin, atau logika pemrosesannya berbentuk graf kompleks.
Menyusun topologi sumber-filter-sink#
Topologi pada diagram sebelumnya dinyatakan dalam beberapa baris DSL. Potongan Java berikut adalah bagian proyek Maven atau Gradle dengan dependensi kafka-streams, sehingga tidak dieksekusi di lingkungan penulisan dan berfungsi sebagai bahan bacaan. Tiga pemanggilan pertama menyusun graf, sedangkan baris-baris terakhir memulai aplikasinya.
// tidak-dijalankan
StreamsBuilder builder = new StreamsBuilder();
KStream<String, Double> suhu = builder.stream("suhu-ruang"); // processor sumber
KStream<String, Double> panas = suhu.filter(
(sensor, nilai) -> nilai >= 30.0); // processor filter
panas.mapValues(nilai -> String.format("%.1f C", nilai))
.to("suhu-panas"); // processor sink
Properties properti = new Properties();
properti.put(StreamsConfig.APPLICATION_ID_CONFIG, "penyaring-suhu");
properti.put(StreamsConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
KafkaStreams aplikasi = new KafkaStreams(builder.build(), properti);
aplikasi.start();Metode builder.stream membentuk processor sumber, filter menyisipkan processor penyaring, mapValues mengubah nilai tanpa menyentuh key, dan to menutup graf dengan processor sink. Metode build() mengumpulkan seluruh node menjadi topologi yang dijalankan objek KafkaStreams. Nilai application.id menjadi identitas consumer group aplikasi, sehingga beberapa instance aplikasi yang sama otomatis membagi partisi. Setelah start() dipanggil, tiap task memproses record dari partisinya sepanjang aplikasi berjalan.
Simulasi Python berikut memutar data yang sama melalui processor yang sama tanpa broker, sehingga keluaran tiap tahap dapat diperiksa langsung. Data ujinya adalah lima bacaan suhu, dan processor filter menahan bacaan di bawah 30 derajat sebelum sink menulis sisanya.
# simulasi topologi sumber -> filter -> mapValues -> sink tanpa broker
bacaan_suhu = [
("sensor-1", 21.5),
("sensor-2", 34.0),
("sensor-1", 27.8),
("sensor-2", 19.2),
("sensor-3", 31.4),
]
AMBANG = 30.0
def sumber():
# processor sumber: membaca record dari topic suhu-ruang
for key, nilai in bacaan_suhu:
yield key, nilai
def filter_panas(record):
# processor filter: record lolos bila nilai minimal 30
key, nilai = record
return nilai >= AMBANG
def map_format(record):
# processor mapValues: nilai diformat menjadi teks, key tidak berubah
key, nilai = record
return key, f"{nilai:.1f} C"
records = list(sumber())
print("sumber: membaca topic suhu-ruang")
for key, nilai in records:
print(f" record key={key} nilai={nilai}")
print(f"filter: lolos bila nilai >= {AMBANG}")
lolos = [record for record in records if filter_panas(record)]
for key, nilai in lolos:
print(f" lolos key={key} nilai={nilai}")
print("mapValues: format nilai, lalu sink menulis ke topic suhu-panas")
for key, nilai in lolos:
key_hasil, teks = map_format((key, nilai))
print(f' tulis key={key_hasil} nilai="{teks}"')sumber: membaca topic suhu-ruang
record key=sensor-1 nilai=21.5
record key=sensor-2 nilai=34.0
record key=sensor-1 nilai=27.8
record key=sensor-2 nilai=19.2
record key=sensor-3 nilai=31.4
filter: lolos bila nilai >= 30.0
lolos key=sensor-2 nilai=34.0
lolos key=sensor-3 nilai=31.4
mapValues: format nilai, lalu sink menulis ke topic suhu-panas
tulis key=sensor-2 nilai="34.0 C"
tulis key=sensor-3 nilai="31.4 C"Keluaran memperlihatkan prinsip yang berlaku pada aplikasi Streams sungguhan, yaitu sumber menghasilkan lima record, filter menyisakan dua record, dan sink hanya menerima record yang lolos. Bacaan 21.5, 27.8, dan 19.2 berhenti di filter dan tidak pernah sampai sink, sedangkan record yang lolos berpindah processor dengan key yang tidak berubah. Pemrosesan record demi record seperti ini berlangsung terus-menerus selama aplikasi berjalan, bukan sekali jalan seperti pada simulasi.
Kesalahan umum#
- Memperlakukan Kafka Streams sebagai cluster pemroses terpisah. Menyiapkan mesin khusus “untuk Streams” dan menganggap broker memegang logika pemrosesan membuang sumber daya, karena seluruh pemrosesan berada di dalam instance aplikasi. Tautkan pustakanya ke aplikasi, jalankan aplikasinya di mana pun JVM tersedia, dan naikkan skalanya dengan menambah instance.
- Menganggap KStream dan KTable dapat dipertukarkan. Agregasi dari KStream menghitung setiap kejadian, sedangkan dari KTable hanya menghitung keadaan yang berubah, sehingga hasil yang diharapkan berupa jumlah kejadian bisa terlihat jauh lebih kecil. Tentukan abstraksinya sebelum menulis operator agregasi, dan ingat hasil agregasi KStream berupa KTable.
- Menambah instance melebihi jumlah partisi input. Setiap task hanya mengikat satu partisi, sehingga instance berlebih tidak memperoleh task dan hanya menambah konsumsi memori serta jaringan. Sesuaikan jumlah partisi topic terlebih dahulu bila skala pemrosesannya perlu dinaikkan.
- Menilai kesehatan aplikasi hanya dari jumlah instance. Instance dapat berdiri tanpa task pada topic yang partisinya sedikit, sehingga jumlah instance tidak menunjukkan pekerjaan yang benar-benar berjalan. Pantau task aktif dan consumer lag pada topic input-nya sebagaimana aplikasi consumer biasa dipantau.
Ringkasan#
- Kafka Streams adalah pustaka di dalam aplikasi JVM, dan paralelismenya membagi task per partisi lewat mekanisme consumer group.
- Pemrosesan dinyatakan sebagai processor topology dari processor sumber, processor transformasi, sampai processor sink, dan dijalankan record demi record.
- KStream menyajikan setiap record sebagai kejadian, sedangkan KTable menyajikan keadaan terkini per key.
- Flink dan Spark menuntut cluster terpisah dengan checkpoint ke storage eksternal, sedangkan Kafka Streams mengandalkan state lokal dengan changelog topic dan transaksi Kafka untuk jaminan tepat sekali.
Referensi#
- Apache Kafka Streams: core concepts — Apache Software Foundation — model processor topology, task, dan perbedaan KStream dengan KTable.
- Apache Kafka Streams: overview — Apache Software Foundation — posisi Kafka Streams sebagai pustaka klien beserta fitur utamanya.
- Apache Flink: overview — Apache Software Foundation — arsitektur cluster Flink yang menjadi pembanding model penerapan.
- Structured Streaming programming guide — Apache Software Foundation — model micro-batch Spark Structured Streaming yang menjadi pembanding model penerapan.