references/state-management.mdMarkdown4 KBView on GitHub
This guide covers state management best practices for Managed Service for Apache Flink applications, including efficient state usage, TTL configuration, state type selection, and Managed Service for Apache Flink-specific state management considerations.
For general development patterns and application structure, see best-practices.md. For serialization guidance, see serialization-guide.md.
Code examples in this guide use Flink 2.2 APIs by default, which are also compatible with Flink 1.20 unless noted otherwise. See flink-2x-migration.md for the complete migration reference.
Use MapState<K, V> whenever you need per-key updates inside a logical map. Storing a Map<K, V> inside ValueState<Map<K, V>> and reading-mutating-writing it on every event is O(map size) per access — RocksDB has to deserialize every entry, your code mutates one, and the whole map gets re-serialized and written back. MapState is O(1) per put/get/remove: each map entry maps to its own RocksDB key, so only the touched entry is read or written.
There is also a state-migration consequence specific to MSF: nested generic collections inside ValueState (e.g. ValueState<Map<String, MyType>>) typically fall back to Kryo serialization, and Kryo-serialized state does not migrate from Flink 1.x to 2.x. MapState uses a dedicated MapSerializer that does carry across the upgrade. See serialization-guide.md for the wider Kryo guidance and flink-2x-migration.md for the migration impact.
public class OptimizedKeyedProcessor extends KeyedProcessFunction<String, Event, Result> {
// Use appropriate state types
private transient ValueState<EventAggregate> aggregateState;
@Override
public void open(OpenContext openContext) throws Exception {
ValueStateDescriptor<EventAggregate> aggregateDescriptor =
new ValueStateDescriptor<>("aggregate", EventAggregate.class);
// TTL configuration - application-level concern
aggregateDescriptor.enableTimeToLive(StateTtlConfig.newBuilder(Duration.ofHours(24))
.setUpdateType(StateTtlConfig.UpdateType.OnCreateAndWrite)
.setStateVisibility(StateTtlConfig.StateVisibility.NeverReturnExpired)
.build());
aggregateState = getRuntimeContext().getState(aggregateDescriptor);
}
@Override
public void processElement(Event event, Context ctx, Collector<Result> out) throws Exception {
// Efficient state access patterns
EventAggregate current = aggregateState.value();
if (current == null) {
current = new EventAggregate();
}
// Update state efficiently
current.update(event);
aggregateState.update(current);
// Use timers for handling events that occur after the input event - e.g. in this case we want to trigger the output an hour after the input occurs
ctx.timerService().registerEventTimeTimer(event.getTimestamp() + 3600000); // 1 hour
}
@Override
public void onTimer(long timestamp, OnTimerContext ctx, Collector<Result> out) throws Exception {
// Handle timer firing - cleanup or emit final results
EventAggregate current = aggregateState.value();
if (current != null) {
// Emit final result or perform cleanup
out.collect(new Result(ctx.getCurrentKey(), current.getFinalValue()));
// Clear state after processing to re-initialize if needed
aggregateState.clear();
}
}
}Managed Service for Apache Flink service handles: