Definition
- 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.
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
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.
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
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 |
// 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.
// 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.
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.
// 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.
// 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.
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
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.
---