Episode ini membahas pola-pola streaming lanjutan: stateful joins, stream-stream joins, dan temporal joins, hopping windows dan sessionization, iterative streaming dengan feedback loops, serta hybrid batch dan stream processing.

Episode-episode sebelumnya membangun kemampuan dasar yang kuat. Episode 17 ini menggabungkannya menjadi pola-pola tingkat lanjut yang muncul berulang kali di dunia nyata: menggabungkan dua stream, membangun sesi pengguna, memproses data secara iteratif, dan menyatukan batch dengan stream dalam satu pipeline.
Kita akan membahas stateful joins dalam tiga bentuk — window join, interval join, dan temporal join — lalu hopping windows dan sessionization, iterative streaming dengan feedback loops, serta hybrid batch dan stream processing. Ini episode yang membuat kalian "naik kelas" sebagai stream engineer.
Window join mencocokkan elemen dari dua stream yang berada dalam window yang sama:
import org.apache.flink.api.java.tuple.Tuple2;
import org.apache.flink.streaming.api.windowing.assigners.TumblingEventTimeWindows;
import org.apache.flink.streaming.api.windowing.time.Time;
orders.join(payments)
.where(Order::getOrderId)
.equalTo(Payment::getOrderId)
.window(TumblingEventTimeWindows.of(Time.minutes(5)))
.apply((order, payment) -> Tuple2.of(order, payment))
.print();.where(...).equalTo(...) menetapkan key join, dan .window membatasi kecocokan pada window yang sama. Ini pola paling sederhana untuk menggabungkan event dari dua sumber.
Interval join mencocokkan elemen berdasarkan waktu relatif, tanpa window yang kaku:
import org.apache.flink.streaming.api.functions.co.ProcessJoinFunction;
import org.apache.flink.streaming.api.windowing.time.Time;
orders.keyBy(Order::getUserId)
.intervalJoin(actions.keyBy(Action::getUserId))
.between(Time.minutes(-30), Time.minutes(30))
.process(new ProcessJoinFunction<Order, Action, JoinedRow>() {
@Override
public void processElement(
Order order, Action action, Context ctx, Collector<JoinedRow> out) {
out.collect(new JoinedRow(order, action));
}
});.between membatasi kecocokan pada jendela waktu relatif terhadap event. Interval join menyimpan state kedua stream selama rentang waktu — pahami biaya state-nya saat stream ber-join sangat besar.
Temporal join mencocokkan event stream dengan tabel dimensi yang berubah seiring waktu:
SELECT o.order_id, o.amount, c.country
FROM orders o
JOIN customer_catalog FOR SYSTEM_TIME AS OF o.event_ts AS c
ON o.customer_id = c.id;FOR SYSTEM_TIME AS OF o.event_ts mengambil versi dimensi yang valid pada saat event terjadi — bukan versi terbaru. Ini jawaban akurat untuk memperkaya event dengan data yang berubah.
Hopping window (sliding) diekspresikan dengan fungsi HOP:
SELECT user_id, SUM(amount) AS total
FROM orders
GROUP BY user_id, HOP(event_ts, INTERVAL '5' MINUTE, INTERVAL '10' MINUTE);HOP(event_ts, INTERVAL '5' MINUTE, INTERVAL '10' MINUTE) membuka window 10 menit setiap 5 menit — setiap event masuk ke dua window sekaligus.
Sessionization mengelompokkan aktivitas pengguna menjadi sesi berdasarkan gap waktu:
SELECT user_id,
SESSION_START(event_ts, INTERVAL '30' MINUTE) AS mulai,
SESSION_END(event_ts, INTERVAL '30' MINUTE) AS selesai,
COUNT(*) AS aktivitas
FROM clicks
GROUP BY user_id, SESSION(event_ts, INTERVAL '30' MINUTE);SESSION_START dan SESSION_END memberi batas tiap sesi. Pola ini sangat umum untuk analitik produk: berapa lama pengguna bertahan, berapa aktivitas per sesi, dan kapan sesi paling sering terjadi.
Beberapa algoritma membutuhkan pengulangan sampai konvergen — misalnya penghitungan PageRank. Flink menyediakan operator iterate:
import org.apache.flink.streaming.api.datastream.IterativeStream;
IterativeStream<Long> iteration = numbers.iterate();
DataStream<Long> proses = iteration.map(step);
DataStream<Long> selesai = proses.filter(konvergen);
iteration.closeWith(proses.filter(belumKonvergen));
selesai.print();iteration.closeWith mengirim hasil yang belum konvergen kembali ke awal iterasi, sedangkan filter konvergen mengeluarkan hasil akhir. Ingat: setiap putaran menambah latency, jadi gunakan hanya untuk komputasi yang benar-benar butuh umpan balik.
Feedback loop menghambat checkpoint dan membuat penjadwalan lebih rumit. Pertimbangkan alternatif: stateful processing dengan MapState, atau loop di luar Flink. Pahami bahwa kesederhanaan pipeline sering lebih berharga daripada kesempurnaan algoritma.
Flink menawarkan pemrosesan batch dan stream dalam satu model pemrograman. DataSet API lama kini didepresiasi — arahnya adalah Table API yang berjalan baik untuk data bounded maupun unbounded.
import org.apache.flink.table.api.EnvironmentSettings;
import org.apache.flink.table.api.TableEnvironment;
EnvironmentSettings settings = EnvironmentSettings.inBatchMode();
TableEnvironment tableEnv = TableEnvironment.create(settings);
tableEnv.executeSql("SELECT user_id, SUM(amount) FROM orders_hist GROUP BY user_id").print();inBatchMode() menjalankan query dengan semantik batch (hasil penuh saat query selesai). Kode yang sama bisa dijalankan dalam mode streaming — hanya context-nya yang berubah. Inilah kekuatan penyatuan model batch dan stream di Flink.
Episode 17 memperluas repertoar kalian: window join, interval join, dan temporal join untuk menggabungkan data; hopping window dan sessionization untuk analitik berbasis waktu; iterative streaming untuk komputasi berulang; serta hybrid batch dan stream lewat Table API.
Inti yang harus dibawa pulang:
Di episode 18 selanjutnya kita akan membahas custom connectors & extensions — membuat custom source dan sink, memperluas Flink dengan serializer dan codec kustom, memahami operator lifecycle dan checkpoint hooks, hingga berkontribusi ke ekosistem connector. Kalian akan belajar membangun jembatan Flink ke sistem yang belum ada connector resminya.