Back to Spark Streaming & Structured Streaming
Q15Question 15 of 22

What are Flink state backends

Key Concepts
  • HashMapStateBackend stores state as Java objects on the JVM heap with the fastest access but is bounded by heap size.
  • EmbeddedRocksDBStateBackend stores serialized bytes on local disk via RocksDB, supporting state far larger than memory.
  • RocksDB enables incremental checkpoints that upload only changed files, making checkpoint cost proportional to the delta.
  • HashMap checkpoints are full snapshots whose cost grows with total state, often making them impractical for many-gigabyte state.
  • RocksDB keeps state off-heap, avoiding JVM garbage-collection pressure for large state.
  • Most large production Flink jobs use RocksDB because incremental checkpointing and disk-scale state are essential for real-world workloads.

Definition

HashMapStateBackend is heap-fast but memory-bounded, while EmbeddedRocksDBStateBackend is disk-backed and handles huge state but is slower per access, so you pick based on state size versus latency needs.
  • HashMapStateBackend – Keeps all state as Java objects on the JVM heap; fastest per-access latency, but bounded by heap size and subject to garbage-collection pressure.
  • EmbeddedRocksDBStateBackend – Stores state as serialized bytes in an embedded RocksDB instance on local disk; supports state far larger than memory and enables incremental checkpoints.
  • Incremental checkpoints – RocksDB uploads only changed state files since the last checkpoint, so checkpoint cost scales with the delta, not the total state.
  • Choice criterion – If state fits in heap and you need lowest latency, use HashMap; if state is large or growing, use RocksDB and accept the per-access serialization overhead.
  • Off-heap memory – RocksDB uses native memory outside the JVM heap, which must be budgeted in task manager configuration.

Think of the National Bank of India's clerk keeping customer passbooks either on the desk or in a filing cabinet. HashMapStateBackend is like keeping every passbook open on the desk: instant access, but the desk only holds so many before it overflows. EmbeddedRocksDBStateBackend is like a well-indexed filing cabinet: room for millions of passbooks, but each lookup costs a few extra seconds to pull the drawer.

Understanding the Concepts

What is the HashMapStateBackend?

The HashMapStateBackend holds state as live Java objects in a hash map on the task manager heap. Reads and writes are extremely fast with no serialization, giving the lowest per-access latency.

JAVA
import org.apache.flink.runtime.state.hashmap.HashMapStateBackend;

StreamExecutionEnvironment env =
    StreamExecutionEnvironment.getExecutionEnvironment();

// Configure HashMap state backend
env.setStateBackend(new HashMapStateBackend());
env.enableCheckpointing(10_000);  // checkpoint every 10 seconds

// State lives on JVM heap as plain Java objects
// Access: O(1) hash lookup, no serialization
TEXT
HashMapStateBackend:
  Storage:      JVM heap (Java objects)
  Access speed: Fastest (no serialization)
  Max state:    Limited by heap size (typically 1-50 GB)
  GC pressure:  High for large state
  Checkpoint:   Full snapshot (all state serialized and uploaded)

What is the EmbeddedRocksDBStateBackend?

The EmbeddedRocksDBStateBackend stores state as serialized bytes in an embedded RocksDB key-value store on local disk. Every access serializes the key, looks it up in RocksDB, and deserializes the value.

JAVA
import org.apache.flink.contrib.streaming.state.EmbeddedRocksDBStateBackend;

// Configure RocksDB state backend
env.setStateBackend(new EmbeddedRocksDBStateBackend(true));
// true = enable incremental checkpoints
env.enableCheckpointing(10_000);

// State lives on local SSD via RocksDB
// Access: serialize key → RocksDB lookup → deserialize value
TEXT
EmbeddedRocksDBStateBackend:
  Storage:      Local disk (serialized bytes in RocksDB)
  Access speed: Slower (serialization + disk I/O)
  Max state:    Hundreds of GB to TB (spills to disk)
  GC pressure:  Low (state is off-heap)
  Checkpoint:   Incremental (only changed files uploaded)

How are they different?

| Aspect | HashMapStateBackend | EmbeddedRocksDBStateBackend |
|---|---|---|
| Storage | JVM heap | Local disk (RocksDB) |
| Access latency | Lowest (no serialization) | Higher (serialize/deserialize) |
| State size limit | Bounded by heap | Bounded by disk |
| GC pressure | High for large state | Low (off-heap) |
| Checkpoint type | Full snapshot | Incremental (only delta) |
| Best for | Small state, low latency | Large state, long-running jobs |

JAVA
// Small state job: HashMap
env.setStateBackend(new HashMapStateBackend());
// e.g., 1000 admin accounts, microsecond access

// Large state job: RocksDB
env.setStateBackend(new EmbeddedRocksDBStateBackend(true));
// e.g., 100 million retail accounts, incremental checkpoints

Why do incremental checkpoints matter?

With HashMap, every checkpoint serializes and uploads all state. With RocksDB, Flink uploads only the RocksDB files that changed since the last checkpoint, so checkpoint cost scales with the delta. This is often the deciding factor for large-state jobs.

JAVA
// HashMap: full snapshot every checkpoint
// State = 50 GB → every checkpoint uploads 50 GB
// Checkpoint time: minutes

// RocksDB incremental: only delta
// State = 50 GB, changed since last = 500 MB
// Checkpoint uploads only 500 MB
// Checkpoint time: seconds

How do you configure RocksDB for performance?

RocksDB benefits from fast local SSDs and from tuning its block cache and write buffers. You must also budget off-heap native memory alongside the JVM.

JAVA
import org.apache.flink.contrib.streaming.state.EmbeddedRocksDBStateBackend;
import org.apache.flink.contrib.streaming.state.PredefinedOptions;

EmbeddedRocksDBStateBackend rocksDB =
    new EmbeddedRocksDBStateBackend(true);  // incremental
rocksDB.setPredefinedOptions(
    PredefinedOptions.SPINNING_DISK_OPTIMIZED_HIGH_MEM);
env.setStateBackend(rocksDB);

// Flink config for memory:
// taskmanager.memory.managed.fraction: 0.4  (for RocksDB cache)
// state.backend.rocksdb.memory.managed: true

When do you use each?

The choice comes down to two questions: how big is your state, and how tight is your latency budget.

JAVA
// Use case 1: Failed login tracker (small state, low latency)
// ~1000 admin accounts, need microsecond state access
env.setStateBackend(new HashMapStateBackend());

// Use case 2: Retail fraud scoring (huge state)
// 100 million accounts, 90-day rolling history per account
// State = hundreds of GB, must checkpoint efficiently
env.setStateBackend(new EmbeddedRocksDBStateBackend(true));

What are common mistakes?

The most common mistake is using HashMapStateBackend for a large-state job, which leads to heap exhaustion and long GC pauses. Another is forgetting to budget native memory for RocksDB.

JAVA
// WRONG -- HashMap for 100M accounts
env.setStateBackend(new HashMapStateBackend());
// State exceeds heap → OutOfMemoryError
// Full checkpoint of 50 GB → timeout

// CORRECT -- RocksDB for large state
env.setStateBackend(new EmbeddedRocksDBStateBackend(true));
// State spills to disk, incremental checkpoints stay fast

What about state TTL?

Both backends support state time-to-live (TTL) to automatically expire old state entries, which is useful for bounding state in non-windowed keyed operations.

JAVA
import org.apache.flink.api.common.state.StateTtlConfig;
import org.apache.flink.api.common.time.Time;

StateTtlConfig ttlConfig = StateTtlConfig
    .newBuilder(Time.days(90))  // expire after 90 days
    .setUpdateType(StateTtlConfig.UpdateType.OnCreateAndWrite)
    .setStateVisibility(
        StateTtlConfig.StateVisibility.NeverReturnExpired)
    .build();

ValueStateDescriptor<BigDecimal> desc =
    new ValueStateDescriptor<>("balance", BigDecimal.class);
desc.enableTimeToLive(ttlConfig);

What should a senior engineer know?

Most large production Flink jobs at a bank run on RocksDB because per-account state across hundreds of millions of accounts is huge and must be durable. The per-access overhead of RocksDB is often negligible relative to network and sink latency. The decisive factor is usually incremental checkpointing, which keeps many-gigabyte state checkpointable in seconds instead of minutes.

Visual Overview

DIAGRAM
  State Backend Comparison

  HashMapStateBackend:          EmbeddedRocksDBStateBackend:
  ┌──────────────────┐          ┌──────────────────┐
  │  JVM Heap        │          │  Local Disk (SSD) │
  │  ┌────────────┐  │          │  ┌────────────┐  │
  │  │ Java Object│  │          │  │ Serialized │  │
  │  │ key=ACC001 │  │          │  │ bytes in   │  │
  │  │ val=150000 │  │          │  │ RocksDB    │  │
  │  └────────────┘  │          │  └────────────┘  │
  │  Access: O(1)    │          │  Access: ser/de  │
  │  Max: ~50 GB     │          │  Max: TB         │
  │  GC: HIGH        │          │  GC: LOW         │
  └────────┬─────────┘          └────────┬─────────┘
           │                             │
           ▼                             ▼
  Full Checkpoint              Incremental Checkpoint
  ┌──────────────┐             ┌──────────────┐
  │ Upload ALL   │             │ Upload only  │
  │ 50 GB state  │             │ changed SST  │
  │ every time   │             │ files (500MB)│
  └──────────────┘             └──────────────┘
  Time: minutes                Time: seconds

Key Takeaway

A Flink state backend decides where your operator's working state lives and how it is organized for checkpointing. The HashMapStateBackend keeps all state as Java objects on the JVM heap, giving the fastest per-access latency with no serialization. Its limits are that state is bounded by heap, large heaps cause GC pauses, and its checkpoints are full snapshots whose cost grows with total state.

The EmbeddedRocksDBStateBackend stores state as serialized bytes in an embedded RocksDB on local disk. Access is slower due to serialization, but state can scale to hundreds of gigabytes or terabytes by spilling to disk, GC pressure is low because state is off-heap, and incremental checkpoints upload only changed files, making checkpoint cost proportional to the delta.

The choice is straightforward. If state fits comfortably in heap and you need minimal latency, use HashMap. If state is large, growing, or must checkpoint efficiently, use RocksDB. In practice, most large production Flink jobs at a bank use RocksDB because per-account state across hundreds of millions of accounts far exceeds heap capacity.

At the National Bank of India a small admin-login tracker ran on HashMap for microsecond access, while the massive retail fraud-scoring job with a 90-day rolling history per account ran on RocksDB. Incremental checkpoints kept the large job's checkpoint time in seconds rather than minutes.

Remember one sentence: HashMap is heap-fast but memory-bounded, RocksDB is disk-backed and handles huge state with incremental checkpoints, and incremental checkpointing is usually the deciding factor for large-state jobs.

---

Share
Use ← → arrow keys to navigate