This episode optimizes Flink job performance: rearranging the job graph and operator chaining, tuning the checkpoint interval and state size, garbage collection tuning, and measuring throughput and end-to-end latency to verify every change.

Episode 14 managed resources; episode 15 optimizes how Flink works internally. Many jobs "run" but are wasteful: operators that could be merged are kept separate, checkpoints that are too frequent grind down throughput, or state balloons without limit. Tuning is the science of eliminating that waste.
We'll optimize the job graph and operator chaining, tune the checkpoint interval and state size, tune garbage collection, and close with how to measure throughput and end-to-end latency. Every practice in this episode follows one principle: measure first, change, then measure again.
Flink merges adjacent stateless operators into one task to avoid serialization overhead between operators. The result: higher throughput, lower latency, and more efficient resources. Chaining happens automatically — and you can control it:
source
.map(new ParseOrder()).startNewChain()
.keyBy(Order::getUserId)
.window(TumblingEventTimeWindows.of(Time.minutes(1)))
.reduce(new SumOrder()).disableChaining()
.sinkTo(sink);startNewChain() forces the map operator to start a new chain, and disableChaining() separates reduce so it doesn't merge with the next operator. A rule of thumb: keep the default chaining, and separate only when you need isolation (for example, for different parallelism).
On the web dashboard, the job graph shows chaining as task blocks. The fewer blocks for the same workload, the more efficient. If you see many small separate operators that could be chained, consider simplifying the pipeline.
Checkpoints protect you from failure, but every checkpoint consumes resources. Too-frequent intervals reduce throughput; too-rare ones lengthen recovery time.
execution.checkpointing.interval: 2min
execution.checkpointing.min-pause: 30s
execution.checkpointing.tolerable-failed-checkpoints: 2
execution.checkpointing.unaligned.enabled: trueexecution.checkpointing.unaligned.enabled allows checkpoints without waiting for barriers to spread — speeding up checkpoints on jobs with high backpressure, at the trade-off of larger state. Use it only if standard checkpoints are slow.
State that never expires is the most common waste. Give state a TTL:
import org.apache.flink.api.common.state.StateTtlConfig;
import org.apache.flink.api.common.time.Time;
StateTtlConfig ttlConfig = StateTtlConfig
.newBuilder(Time.hours(24))
.setUpdateType(StateTtlConfig.UpdateType.OnReadAndWrite)
.cleanupInRocksdbCompactFilter(1000)
.build();cleanupInRocksdbCompactFilter cleans up expired state during RocksDB compaction — without it, TTL is only marked and not actually discarded until accessed. Slim state makes checkpoints lighter and GC happier.
Jobs with many temporary objects trigger garbage collection that can spike latency. JVM options for the TaskManager are set in config.yaml:
env.java.opts.taskmanager: -XX:+UseG1GC -Xms1g -Xmx1g
taskmanager.memory.managed.fraction: 0.4env.java.opts.taskmanager injects JVM flags. G1GC is the modern default that splits the heap into regions and minimizes pauses. Reduce temporary allocations in code (for example, reusing objects in operators) because that has a bigger impact than any JVM flag.
taskmanager.memory.managed.fraction partitions the heap for state. The larger it is for RocksDB, the smaller it is for objects and GC — find the balance by watching the heap usage and GC duration metrics on the dashboard.
For benchmarks, use DataGen as the source so the load is controlled:
./bin/flink run -d -p 4 target/bench-job.jarOnce running, read the metrics from the REST API:
curl -s http://localhost:8081/jobs/overviewcurl -s http://localhost:8081/jobs/overview gives the job list; from there the numRecordsOutPerSecond metric shows throughput, and the watermark latency metric shows end-to-end delay. Record the baseline before tuning and compare after every change.
End-to-end latency is measured from the watermark metric: the gap between the latest event time and the current processing time shows how far the pipeline is behind. Latency that rises together with backpressure usually points to a specific operator — fix that operator rather than tuning globally.
measure the baseline → change one variable → measure again → keep the better oneEpisode 15 turned a running job into an efficient job: arranging the job graph with proper chaining, tuning the checkpoint interval and state TTL, tuning GC via JVM options, and benchmarking throughput and latency with measurable metrics.
The key takeaways:
In the next episode, episode 16, we'll discuss savepoints, upgrades & migration — understanding the savepoint lifecycle and version compatibility, performing job upgrades and state migration, rollback strategies, and testing savepoint restores in staging. You'll learn to change production jobs without losing a single piece of state.