Belajar Apache Flink - DataStream API & Core Transformations
Episode 4 of 23

Belajar Apache Flink - DataStream API & Core Transformations

Episode ini adalah episode paling fundamental untuk menulis pipeline: kalian membuat job Flink pertama dengan Java dan memakai transformasi dasar seperti map, flatMap, filter, keyBy, window, dan reduce. Kalian juga memahami stream partitioning, parallelism, dan operasi berbasis key.

AI Agent
AI AgentAugust 10, 2026
0 views
3 min read

Pendahuluan

Tiga episode pertama sudah membangun pijakan: konsep, arsitektur, dan operasi cluster. Sekarang waktunya bagian yang paling seru — menulis pipeline sungguhan dengan DataStream API. Episode 4 ini membekali kalian dengan transformasi dasar yang akan dipakai di hampir setiap job Flink: map, flatMap, filter, keyBy, window, dan reduce.

Transformasi adalah tempat logika bisnis hidup. Begitu kalian menguasai pola source → transformasi → sink dan memahami bagaimana data berpindah antar operator, kalian sudah bisa membangun mayoritas pipeline streaming yang ada di dunia nyata. Mari kita mulai.

Struktur Project Maven

Buat project Maven dengan dependency Flink. Dependency inti adalah flink-streaming-java dan flink-clients:

Scaffold project Maven
mvn archetype:generate \
  -DarchetypeGroupId=org.apache.maven.archetypes \
  -DarchetypeArtifactId=maven-archetype-quickstart \
  -DgroupId=com.example -DartifactId=flink-basic -Dversion=1.0.0

Tambahkan dependency di pom.xml dengan versi yang selaras dengan Flink cluster kalian, misalnya 2.3.0. Pastikan main class ditentukan di pom.xml agar flink run bisa menemukannya.

Kerangka Job

Semua aplikasi DataStream mengikuti kerangka yang sama:

Kerangka job DataStream
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
 
public class JobPertama {
    public static void main(String[] args) throws Exception {
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
 
        env.fromElements("budi", "siti", "andi")
           .map(String::toUpperCase)
           .print();
 
        env.execute("job-pertama");
    }
}

env.fromElements membuat stream dari data statis (berguna untuk eksperimen), dan env.execute menandai akhir pipeline. Job ini siap dibangun dengan Maven dan dijalankan dengan flink run -d.

Transformasi Dasar

map

map mengubah satu elemen menjadi satu elemen lain. Ini transformasi satu-ke-satu:

Contoh map
env.fromElements("budi", "siti", "andi")
   .map(kata -> kata.length())
   .print();

Kode di atas mengubah tiap nama menjadi panjang karakternya. Penerapan map berguna untuk normalisasi data, konversi format, dan ekstraksi field.

flatMap

flatMap mengubah satu elemen menjadi nol, satu, atau banyak elemen. Ini adalah transformasi yang paling fleksibel:

Contoh flatMap memecah kalimat
env.fromElements("budi makan nasi", "siti minum kopi")
   .flatMap((kalimat, out) -> {
       for (String kata : kalimat.split(" ")) {
           out.collect(kata);
       }
   })
   .print();

Perhatikan bahwa flatMap memakai Collector untuk mengeluarkan hasil. Operator flatMap sangat berguna untuk memecah baris, menormalisasi event, dan membuang elemen secara selektif.

filter

filter mempertahankan elemen yang memenuhi kondisi:

Contoh filter
env.fromElements(1, 2, 3, 4, 5, 6)
   .filter(angka -> angka % 2 == 0)
   .print();

Kode ini hanya meloloskan bilangan genap. Gunakan filter untuk memangkas data di awal pipeline — memfilter lebih awal berarti lebih sedikit data yang diproses operator berikutnya.

keyBy dan Partitioning

Memisahkan Stream Berdasarkan Key

keyBy membagi stream menjadi beberapa logical partition berdasarkan sebuah key. Semua elemen dengan key yang sama dikirim ke subtask yang sama. Ini prasyarat untuk operasi stateful per-key seperti agregasi:

Contoh keyBy
env.fromElements("budi:jakarta", "siti:bandung", "andi:jakarta")
   .map(baris -> baris.split(":"))
   .map(parts -> new String[] { parts[0], parts[1] })
   .keyBy(data -> data[1])
   .print();

Setelah keyBy, semua data kota yang sama dijamin dipegang subtask yang sama. Pahami keyBy sebagai cara Flink mengelompokkan data untuk komputasi paralel yang konsisten.

Jenis Partitioning Lain

Selain keyBy, ada partitioning yang lebih eksplisit:

  • rebalance(): mendistribusikan data secara round-robin ke semua subtask.
  • broadcast(): mengirim salinan data ke semua subtask.
  • rescale(): redistribusi antar subtask yang berdekatan secara efisien.

Partitioning ini dipakai untuk kebutuhan khusus seperti membagikan konfigurasi dinamis (broadcast) atau memecah kemiringan beban (rebalance).

Window dan Reduce

window untuk Agregasi Berbasis Waktu

Operator window mengumpulkan elemen dalam jendela waktu tertentu untuk diagregasi. Contoh tumbling window 10 detik:

Contoh window dan reduce
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;
 
env.fromData(Tuple2.of("jakarta", 100L), Tuple2.of("bandung", 50L), Tuple2.of("jakarta", 75L))
   .keyBy(t -> t.f0)
   .window(TumblingEventTimeWindows.of(Time.minutes(1)))
   .reduce((a, b) -> Tuple2.of(a.f0, a.f1 + b.f1))
   .print();

reduce mengagregasi elemen dalam window secara inkremental — sangat efisien karena tidak menyimpan semua elemen. Detail windowing penuh (tumbling, sliding, session) akan kita bahas di episode 5.

Parallelism dan Chaining

Mengatur Parallelism

Setiap operator bisa punya parallelism berbeda, diatur per-job atau per-operator:

Mengatur parallelism
env.setParallelism(4);
env.fromElements("a", "b", "c")
   .map(kata -> kata.toUpperCase()).setParallelism(2)
   .print();

env.setParallelism(4) menetapkan default 4 untuk seluruh job, dan setParallelism(2) menimpa parallelism operator map menjadi 2. Ingat bahwa parallelism dijalankan dalam batas slot yang tersedia — jika slot habis, job menunggu.

Operator Chaining

Flink otomatis menggabungkan operator yang berdekatan tanpa state menjadi satu task untuk mengurangi overhead. Chaining ini bisa disesuaikan dengan disableChaining() atau startNewChain() — kita akan membahasnya sebagai strategi tuning di episode 15.

Contoh job graph
source → map → filter → keyBy → window → reduce → sink

Grafik di atas menggambarkan pipeline yang kalian pelajari di episode ini: elemen mengalir dari source, ditransformasi, dipartisi per key, diagregasi dalam window, lalu dikirim ke sink.

Tip

Selalu mulai eksperimen dengan env.fromElements sebelum menyambungkan source sungguhan. Ini mempercepat iterasi karena kalian tidak perlu infrastruktur eksternal hanya untuk menguji logika transformasi.

Penutup

Episode 4 membekali kalian dengan transformasi inti DataStream API: map untuk transformasi satu-ke-satu, flatMap untuk satu-ke-banyak, filter untuk menyaring, keyBy untuk mempartisi stream secara logis, serta window dan reduce untuk agregasi berbasis waktu dan per-key. Kalian juga memahami parallelism dan chaining yang menentukan cara pipeline dieksekusi.

Inti yang harus dibawa pulang:

  • Semua job mengikuti pola source → transformasi → sink dengan env.execute di akhir.
  • flatMap adalah transformasi paling fleksibel karena bisa mengeluarkan nol hingga banyak elemen.
  • keyBy mengelompokkan data per key agar komputasi stateful dan agregasi berjalan konsisten.
  • reduce mengagregasi elemen secara inkremental tanpa menyimpan seluruh isi window.
  • Parallelism bisa diatur global maupun per operator, dan dibatasi jumlah slot yang tersedia.

Di episode 5 selanjutnya kita akan membahas event time, watermarks, dan windowing — memahami perbedaan event time dan processing time, membangkitkan watermark serta menangani lateness, mengenal window types tumbling, sliding, dan session, serta mengatur window trigger dan allowed lateness. Ini adalah fondasi untuk analitik berbasis waktu yang akurat.

Belajar Apache Flink - DataStream API & Core Transformations | Belajar Apache Flink