references/job-graph-architecture.mdMarkdown9 KBView on GitHub
This guide covers Flink job graph design for Managed Service for Apache Flink applications: operator chaining, operator-to-task-slot mapping, and task slot overload diagnosis. Use it when designing a new job graph, diagnosing performance problems in an existing one, or deciding whether to split a large application. For anti-patterns (data skew, monolith jobs, high fan-out), see job-graph-anti-patterns.md.
Flink automatically chains operators that meet all of these conditions into a single task:
Chained operators run in the same thread, eliminating serialization/deserialization overhead and thread context switching between them. In the Flink Web UI, chained operators appear as a single box in the job graph with names joined by arrows (e.g., Source → Map → Filter).
Verifying chaining in the Flink Web UI:
Diagnosing unexpected chain breaks — work this list in order before assuming a bug or re-running the job:
setParallelism() overrides the env default.keyBy(), rebalance(), shuffle(), broadcast(), or rescale() between two operators inserts a network exchange and breaks the chain at that point. The exchange-label arrow (HASH, REBALANCE, FORWARD) between job-graph boxes tells you which case applies.startNewChain() or disableChaining() left in code. These are commonly added for diagnostic isolation (see below) and forgotten.slotSharingGroup("...") on one operator that differs from its neighbor will prevent chaining even if everything else aligns.Breaking a chain is not always wrong — see “Using disableChaining() and startNewChain() Strategically” below for legitimate reasons (operator-metric isolation, external-call latency separation, explicit parallelism boundaries). The Flink Web UI is the source of truth here, not “the display might be misleading.”
Each task slot runs one parallel pipeline of chained operators. The number of operators per task slot depends on how many operators chain together and how many slot sharing groups exist.
Rule of thumb: 20–40 operators per task slot. This range balances resource utilization against overhead:
| Operators per Task Slot | Behavior |
|---|---|
| < 20 | Underutilized slots; consider consolidating operators or reducing KPUs |
| 20–40 | Healthy range for most workloads |
| 40–100 | Monitor GC pressure and checkpoint duration closely |
| 100–200 | Likely experiencing performance degradation; consider restructuring |
| > 200 | Split the job or restructure the graph (see Operator-to-Task-Slot Overload) |
Break chains only when you have a specific reason:
// Break the chain before a CPU-intensive operator to isolate its metrics
DataStream<Result> results = events
.keyBy(Event::getKey)
.process(new ExpensiveProcessor())
.startNewChain() // This operator starts a new chain
.uid("expensive-processor-uid");
// Completely disable chaining for a specific operator (rarely needed)
DataStream<Enriched> enriched = events
.map(new ExternalServiceLookup())
.disableChaining() // Runs in its own task, not chained with anything
.uid("external-lookup-uid");When to break chains:
startNewChain() makes the intent explicitWhen NOT to break chains:
The Flink Web UI shows two views of the job:
To check operator-to-task-slot assignments:
For data skew detection and mitigation, the monolith job anti-pattern, and the high fan-out anti-pattern, see job-graph-anti-patterns.md.
Each task slot runs a parallel pipeline of chained operators within a single thread. The more operators packed into a slot, the more work that thread must perform — including state access, serialization, timer management, and checkpoint barrier handling.
Recommended ratio: 20–40 operators per task slot.
| Ratio Range | Impact |
|---|---|
| 20–40 | Optimal. Checkpoint barriers propagate quickly, GC pressure is manageable, per-operator metrics remain meaningful. |
| 40–100 | Elevated GC pressure from increased object allocation. Checkpoint duration starts to grow as more state must be snapshotted per slot. Latency percentiles widen. |
| 100–200 | Noticeable degradation. GC pauses become frequent, checkpoint durations may approach the checkpoint interval, and tail latency increases significantly. |
| > 200 | Critical. Split the job or restructure the graph. At this density, GC overhead dominates CPU time, checkpoints risk timing out, and individual operator metrics become unreliable. |
heapMemoryUtilization sustained above 80% (scale-up signal; see monitoring-and-metrics.md), frequent full GC pauseslastCheckpointDuration increasing or approaching the checkpoint intervalbusyTimeMsPerSecond approaching 1000 (fully saturated)ProcessFunction; remove redundant operatorsDataStream<Result> results = events
.keyBy(Event::getKey)
.process(new HeavyProcessor())
.slotSharingGroup("heavy-processing")
.uid("heavy-processor-uid");Flink’s slot sharing allows operators from different pipeline parts to share the same task slot. When all operators are in the default group and the job has many operators, every slot runs one subtask of every operator — leading to overload.
Strategies: Group operators by resource profile (CPU-intensive vs I/O-bound in separate groups). Use the Flink Web UI’s TaskManagers tab to verify balanced slot utilization.