Back to Apache Flink
Q25Question 25 of 33

How does Flink handle backpressure

Key Concepts
  • Backpressure is Flink's automatic flow-control mechanism where a slow downstream operator causes upstream operators and the source to slow down.
  • It works through credit-based flow control: each receiver advertises free buffer credits to senders, and senders can only transmit when credits are available.
  • No records are ever dropped and no special configuration is required; the system self-regulates to the speed of its slowest stage.
  • Backpressure is a symptom, not a cause: the real job is to find and fix the slow operator using the Web UI and metrics.
  • Common bottleneck causes include a slow external sink, heavy computation, data skew overloading one subtask, or insufficient parallelism.
  • Backpressure interacts with aligned checkpointing by slowing barrier propagation, which is why unaligned checkpoints exist.

Definition

Flink backpressure is automatic buffer-based flow control where a slow stage fills its buffers and the pressure propagates upstream to the source, slowing intake with no data loss and no configuration.
  • Backpressure – The automatic flow-control mechanism by which a slow downstream operator causes upstream operators and the source to slow their production rate.
  • Credit-based flow control – Since Flink 1.5, each receiving subtask advertises free buffer credits to senders, and a sender may only transmit as many buffers as the receiver has credits for.
  • No data loss – Records are never dropped; with a replayable source like Kafka, backpressure means the consumer offset advances more slowly and lag grows temporarily.
  • No configuration – The mechanism is always active as an emergent property of finite buffers; there is nothing to enable.
  • Symptom, not cause – Backpressure tells you something is slow; the engineering response is to find and fix the bottleneck, not suppress the signal.

Imagine the National Bank of India has an assembly line of clerks passing transaction slips down a series of trays. If the last clerk is slow, their in-tray fills up. Once full, the clerk before them has nowhere to put new slips, so their out-tray fills too. Eventually the very first clerk, who takes slips from the incoming mail sack, simply stops pulling new mail because they have nowhere to put it. Nothing is thrown away; the whole line automatically slows to match the slowest clerk, and when that clerk catches up, everyone speeds back up.

Understanding the Concepts

Backpressure is the natural flow-control mechanism in Flink that prevents a fast producer from overwhelming a slow consumer. When a downstream operator cannot keep up, the pressure propagates backwards through the pipeline to the source, which slows its intake. No records are ever lost.

JAVA
package com.bank.streaming;

import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;

public class BackpressureDemo {
    public static void main(String[] args) throws Exception {
        StreamExecutionEnvironment env =
            StreamExecutionEnvironment.getExecutionEnvironment();

        env.addSource(new KafkaSource<>("nbi-transactions"))
            .keyBy(Transaction::getAccountId)
            .process(new FraudScorer())  // fast
            .addSink(new SlowDatabaseSink()); // SLOW: bottleneck

        // SlowDatabaseSink cannot keep up:
        // 1. Sink's input buffers fill
        // 2. FraudScorer's output buffers fill
        // 3. Source reads fewer Kafka records
        // 4. Kafka lag grows but NO DATA LOST
        env.execute("NBI Backpressure Demo");
    }
}

How does credit-based flow control work?

Since Flink 1.5, each receiving subtask advertises to each sending subtask how many buffers (credits) it currently has free. A sender may only transmit as many buffers as the receiver has credits for. When the receiver is slow, credits dry up and the sender blocks.

DIAGRAM
Credit-based flow control:

  Sender (FraudScorer-0)          Receiver (DatabaseSink-0)
  ─────────────────────           ─────────────────────────
  Has data to send                Free buffers: 10
                                  Advertises 10 credits
  Sends 10 buffers ──────────────▶ Receives, processes slowly
                                  Free buffers: 2
                                  Advertises 2 credits
  Sends 2 buffers  ──────────────▶ Still processing
                                  Free buffers: 0
                                  Advertises 0 credits
  BLOCKED (no credits)             Still processing...
  Cannot send more data            Eventually frees 3 buffers
                                  Advertises 3 credits
  Resumes sending  ──────────────▶ Normal flow resumes
JAVA
// No configuration needed!
// Credit-based flow control is ALWAYS active
// It is an emergent property of finite buffers
// You do NOT enable or disable it

How does backpressure propagate through the pipeline?

The pressure propagates hop by hop. When the sink is slow, its buffers fill, it advertises zero credits to the operator before it, that operator's output buffers fill, and so on back to the source. The source, finding no buffers to write into, reads fewer records from Kafka.

JAVA
package com.bank.streaming;

public class PropagationDemo {
    // Pipeline: Source -> Enricher -> Scorer -> Sink
    //
    // Sink is slow (database writes take 50ms each)
    //
    // Propagation:
    // Step 1: Sink buffers fill (0 credits to Scorer)
    // Step 2: Scorer output buffers fill (0 credits to Enricher)
    // Step 3: Enricher output buffers fill (0 credits to Source)
    // Step 4: Source stops reading from Kafka
    //
    // Kafka lag grows but records stay safely in Kafka
    // When sink catches up, credits flow back and pipeline resumes
}
DIAGRAM
Backpressure propagation:

  Kafka ──▶ Source ──▶ Enricher ──▶ Scorer ──▶ Sink (SLOW)
                                                 │
              ◀──── pressure propagates ◀────────┘
              │
  Source slows Kafka consumption
  Kafka lag grows but data is safe

How do you diagnose backpressure?

The Flink Web UI shows per-operator backpressure status. The key metrics are busyTimeMsPerSecond (how much time the operator spends doing work), backPressuredTimeMsPerSecond (how much time it spends waiting because downstream is full), and idleTimeMsPerSecond (how much time it sits idle).

DIAGRAM
Diagnosing with Web UI and metrics:

  Operator           Busy    BackPressured    Idle    Status
  ──────────         ────    ─────────────    ────    ──────
  KafkaSource        10%     85%              5%      Backpressured
  Enricher           15%     80%              5%      Backpressured
  FraudScorer        20%     75%              5%      Backpressured
  DatabaseSink       95%     0%               5%      BOTTLENECK

  Reading: DatabaseSink is 95% busy, 0% backpressured
  --> It IS the bottleneck (busy but not waiting on anyone)
  Everything upstream is mostly backpressured
  --> They are WAITING on the sink
JAVA
// Metrics to monitor via Prometheus/Grafana:
// flink_taskmanager_job_task_busyTimeMsPerSecond
// flink_taskmanager_job_task_backPressuredTimeMsPerSecond
// flink_taskmanager_job_task_idleTimeMsPerSecond
// flink_taskmanager_job_task_numRecordsInPerSecond
// flink_taskmanager_job_task_numRecordsOutPerSecond
// kafka_consumer_records_lag_max

What causes backpressure and how do you fix it?

Common causes include a slow external sink, heavy computation in an operator, data skew overloading one subtask, and insufficient parallelism. Each cause has a different fix.

JAVA
package com.bank.streaming;

public class BackpressureFixes {
    // Cause 1: Slow database sink
    // Fix: batch writes, increase sink parallelism, use async writes
    // .addSink(new BatchingDatabaseSink(batchSize=100))
    //     .setParallelism(4);

    // Cause 2: Heavy computation (e.g., ML model scoring)
    // Fix: increase operator parallelism, optimize model
    // .process(new FraudScorer()).setParallelism(8);

    // Cause 3: Data skew on keyBy
    // Fix: salt keys, pre-aggregate, rebalance before keyBy
    // stream.keyBy(txn -> txn.getAccountId() + "-" + txn.hash() % 4)

    // Cause 4: Insufficient parallelism
    // Fix: increase parallelism and add more TaskManagers
    // env.setParallelism(16); // add more slots to cluster
}
TEXT
Backpressure fix decision tree:

  Is one operator 95%+ busy while upstream is backpressured?
    YES --> that operator is the bottleneck
      Is it a sink?         --> batch writes, more parallelism
      Is it a computation?  --> optimize or increase parallelism
      Is one subtask busy
      while others idle?    --> key skew: salt keys
    NO  --> check for external system issues

How does backpressure interact with checkpointing?

Backpressure slows barrier propagation because barriers travel through the same buffers as data. If buffers are full, barriers are delayed, which lengthens checkpoint duration. This is why Flink introduced unaligned checkpoints.

JAVA
package com.bank.streaming;

import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.streaming.api.environment.CheckpointConfig;

public class UnalignedCheckpointDemo {
    public static void main(String[] args) throws Exception {
        StreamExecutionEnvironment env =
            StreamExecutionEnvironment.getExecutionEnvironment();
        env.enableCheckpointing(30_000);

        // Unaligned checkpoints: barriers can overtake data in buffers
        // Checkpoints complete faster under backpressure
        env.getCheckpointConfig().enableUnalignedCheckpoints();

        // Trade-off: unaligned checkpoints are larger because
        // they include in-flight buffer data in the snapshot
        env.execute("NBI Unaligned Checkpoint Demo");
    }
}
TEXT
Aligned vs unaligned checkpoints under backpressure:

  Aligned:   Barrier waits behind data in full buffers
             Checkpoint duration: 30s+ (slow under backpressure)

  Unaligned: Barrier overtakes data in buffers
             In-flight data snapshot with checkpoint
             Checkpoint duration: 2-5s (fast even under backpressure)
             Trade-off: larger checkpoint size

What are common mistakes when dealing with backpressure?

The most common mistake is trying to fix backpressure by increasing buffer sizes. This only delays the onset and does not fix the underlying throughput mismatch. Another mistake is suppressing backpressure by dropping records, which loses data.

JAVA
// WRONG: increasing buffer sizes to "fix" backpressure
// This only delays the onset, does not fix the throughput mismatch
// taskmanager.memory.network.fraction: 0.3 (was 0.1)
// Result: buffers fill a bit later, but still fill eventually

// WRONG: dropping records to avoid backpressure
// stream.filter(txn -> shouldDrop(txn)) // data loss!

// CORRECT: fix the bottleneck
// 1. Identify the busy operator
// 2. Increase its parallelism, optimize its logic, or fix its sink
// 3. Backpressure clears naturally when throughput matches

What should a senior engineer know about backpressure?

A senior engineer should know that backpressure is healthy self-protection and the correct response is to diagnose and remove the bottleneck rather than suppress the signal. They should also know that network buffer metrics, Kafka consumer lag, and the busy/backpressured/idle time ratios together point to the root cause, and that frequent restarts quietly erode throughput.

JAVA
package com.bank.streaming;

public class SeniorBackpressureChecklist {
    // Senior engineer's backpressure checklist:
    //
    // 1. Check Web UI for per-operator backpressure status
    // 2. Find the operator with high busy%, low backpressured%
    //    (this is the bottleneck)
    // 3. Check for subtask skew (one subtask busy, others idle)
    // 4. Check Kafka consumer lag trend
    // 5. Correlate with checkpoint duration
    //    (if checkpoints slow, consider unaligned checkpoints)
    // 6. Fix the bottleneck:
    //    - Slow sink: batch, async, more parallelism
    //    - Heavy compute: optimize, more parallelism
    //    - Key skew: salt keys, pre-aggregate
    // 7. Verify: backpressure clears, lag drains, throughput matches
    //
    // NEVER: increase buffer sizes as a "fix"
    // NEVER: drop records to avoid backpressure
    // NEVER: ignore it (it will get worse)
}

Visual Overview

DIAGRAM
  Credit-Based Backpressure Flow

  Normal flow (no backpressure):
  Source ──10 credits──▶ Enricher ──10 credits──▶ Sink
  Source ──sends data──▶ Enricher ──sends data──▶ Sink
         (credits available)      (credits available)


  Backpressure (sink slow):
  Source                  Enricher                Sink (SLOW)
    │                       │                       │
    │                       │                       │ buffers fill
    │                       │◀── 0 credits ────────│
    │                       │ output buffers fill   │
    │◀── 0 credits ────────│                       │
    │ stops reading Kafka   │                       │
    │                       │                       │
    │  Kafka lag grows      │                       │
    │  but data is SAFE     │                       │


  Recovery (sink catches up):
  Source                  Enricher                Sink
    │                       │                       │ frees buffers
    │                       │◀── 5 credits ────────│
    │                       │ sends data ──────────▶│
    │◀── 5 credits ────────│                       │
    │ resumes Kafka read    │                       │
    │                       │                       │
    │  Lag drains           │  Normal throughput    │


  Diagnosing the Bottleneck:
  ┌────────────┬──────┬──────────────┬──────┐
  │  Operator  │ Busy │BackPressured │ Idle │
  ├────────────┼──────┼──────────────┼──────┤
  │ Source     │ 10%  │    85%       │  5%  │ ◀── waiting
  │ Enricher   │ 15%  │    80%       │  5%  │ ◀── waiting
  │ Scorer     │ 20%  │    75%       │  5%  │ ◀── waiting
  │ Sink       │ 95%  │     0%       │  5%  │ ◀── BOTTLENECK
  └────────────┴──────┴──────────────┴──────┘

Key Takeaway

Backpressure in Flink is the automatic flow-control mechanism that makes a pipeline self-regulate to the speed of its slowest stage. Since Flink 1.5, it uses credit-based flow control where each receiving subtask advertises free buffer credits to its senders. When a downstream operator is slow, its buffers fill, credits dry up, and the shortage propagates hop by hop back to the source, which reads fewer records from Kafka. No records are ever dropped, and no configuration is required.

The key insight is that backpressure is a symptom, not a disease. When you observe it, your job is to find the slow operator using the Web UI and metrics like busy time, backpressured time, and idle time. The operator with high busy percentage and zero backpressured percentage is the bottleneck. You fix it by increasing that operator's parallelism, optimizing its logic, batching writes to a slow sink, or fixing key skew.

At the National Bank of India, our transaction pipeline experienced backpressure during flash-sale traffic spikes. The database sink was the bottleneck, so the pressure propagated back to the Kafka source, which slowed consumption. The unconsumed transactions waited safely in Kafka as lag. We treated the backpressure as a signal, confirmed the bottleneck with metrics, and cured it by increasing the sink's parallelism and batching writes. We also enabled unaligned checkpoints because backpressure was lengthening checkpoint duration.

The rule is simple: backpressure is healthy self-protection, and the correct response is to diagnose and remove the bottleneck rather than suppress the signal.

Remember one sentence: Backpressure propagates through credit-based buffer flow control from a slow operator back to the source with no data loss and no configuration, and the fix is always to address the bottleneck.

---

Share
Use ← → arrow keys to navigate