Guide · Managed Flink Checkpointing
Managed Flink Checkpointing for MCP Servers — Savepoints, State Backends, Exactly-Once Semantics
Checkpointing is how Managed Apache Flink guarantees that a failed application resumes without re-processing or losing events. Every checkpoint is a consistent snapshot of your application's state (window accumulators, aggregation buffers, Kinesis shard offsets) written to S3. When the application restarts — from a transient task failure, an infrastructure event, or an application update — Flink replays from the last successful checkpoint rather than from the beginning of the stream. Without checkpointing, a Managed Flink application running a 15-minute tumbling window over MCP probe results loses all in-window data on every restart. The three decisions that determine checkpoint behavior in production: checkpoint interval (how much data you're willing to replay on recovery), state backend (MemoryStateBackend for small keyed state vs RocksDB for large aggregation buffers), and processing semantics (exactly-once requires the Kinesis source to commit shard offsets atomically with the state snapshot; at-least-once is faster but allows duplicate processing on recovery).
TL;DR
Set checkpoint interval to 60,000 ms for most MCP probe pipelines (matches typical window granularity and limits replay on recovery to 1 minute of data). Use RocksDBStateBackend with incremental checkpoints enabled for any application maintaining keyed state larger than a few hundred MB. Exactly-once processing requires the Kinesis source connector to be at Flink 1.15+ with the new Kinesis Source (not the legacy KinesisConsumer). Always assign stable operator UIDs with uid("your-operator-name") before taking the first savepoint — changing topology without UIDs makes savepoint restoration fail silently.
Checkpoint vs savepoint — the operational distinction
Both checkpoints and savepoints write a full (or incremental) snapshot of application state to S3, but they serve different purposes and have different lifecycle management:
| Property | Checkpoint | Savepoint |
|---|---|---|
| Who triggers it | Flink runtime automatically (on your configured interval) | You, manually via Managed Flink console / API / CLI |
| Retention | Deleted when newer checkpoint completes (configurable retained count) | Kept indefinitely until explicitly deleted |
| Purpose | Automatic recovery from transient failures | Planned upgrade, topology change, migration to new application version |
| State format | Internal, backend-specific (may not be stable across Flink versions) | Canonical format, stable across patch versions |
| Operator UID requirement | Not required for simple topologies | Required for safe restore after topology changes |
| Creation time | Milliseconds to seconds (incremental) | Seconds to minutes (always full snapshot) |
For an AliveMCP probe analytics pipeline, the operational workflow is: checkpoints run every 60 seconds for automatic failure recovery; savepoints are taken manually before deploying a new application version, changing the probe aggregation window size, or migrating to a new Kinesis stream. If a savepoint restore fails (e.g., because operator UIDs were not assigned), the application starts from the latest checkpoint — and if the checkpoint is also incompatible, Flink starts from LATEST_TRIM_HORIZON on the Kinesis source, re-processing from the oldest available shard data.
Checkpoint configuration in Managed Flink
Managed Flink exposes checkpoint configuration through application properties (key-value pairs mapped to Flink's FlinkJobEnvironment) rather than code-level API calls. The recommended pattern is to read checkpoint settings from application properties at startup:
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.streaming.api.CheckpointingMode;
import com.amazonaws.services.kinesisanalytics.runtime.KinesisAnalyticsRuntime;
Map<String, Properties> appProperties = KinesisAnalyticsRuntime.getApplicationProperties();
Properties checkpointProps = appProperties.getOrDefault("CheckpointConfig", new Properties());
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
// Checkpoint interval — how often Flink takes a snapshot
long intervalMs = Long.parseLong(checkpointProps.getProperty("checkpoint.interval.ms", "60000"));
env.enableCheckpointing(intervalMs);
// Semantics: EXACTLY_ONCE or AT_LEAST_ONCE
String semantics = checkpointProps.getProperty("checkpoint.mode", "EXACTLY_ONCE");
env.getCheckpointConfig().setCheckpointingMode(
semantics.equals("EXACTLY_ONCE") ? CheckpointingMode.EXACTLY_ONCE : CheckpointingMode.AT_LEAST_ONCE
);
// Minimum time between checkpoint completions — prevents checkpoint storms
// after a slow checkpoint causes the application to immediately start another
long minPauseBetweenMs = Long.parseLong(checkpointProps.getProperty("checkpoint.min.pause.ms", "30000"));
env.getCheckpointConfig().setMinPauseBetweenCheckpoints(minPauseBetweenMs);
// Checkpoint timeout — abort checkpoint if it takes longer than this
long timeoutMs = Long.parseLong(checkpointProps.getProperty("checkpoint.timeout.ms", "120000"));
env.getCheckpointConfig().setCheckpointTimeout(timeoutMs);
// Retain the last N checkpoints for manual recovery
env.getCheckpointConfig().setMaxConcurrentCheckpoints(1); // Default; only 1 in-progress at once
In Managed Flink, the S3 checkpoint storage path is configured automatically under s3://aws-kinesis-analytics-us-east-1-{accountId}/jobs/{applicationName}/checkpoints/. You do not need to set setCheckpointStorage explicitly — Managed Flink provisions this path and the associated IAM permissions when you create the application.
State backends: MemoryStateBackend vs RocksDB
The state backend determines where keyed state (window accumulators, aggregation maps, deduplication sets) is stored during normal operation. The choice has significant implications for performance, memory consumption, and checkpoint size:
| Backend | State storage | Read/write latency | Max state size | Best for |
|---|---|---|---|---|
| MemoryStateBackend (default) | JVM heap memory | ~10 µs (in-heap) | Limited by TaskManager heap (~100 MB practical max) | Small keyed state, low-latency window aggregation, development |
| FsStateBackend | JVM heap memory (state); S3 (checkpoints) | ~10 µs (in-heap) | Limited by TaskManager heap | Same as Memory but with durable checkpoint storage |
| RocksDBStateBackend | RocksDB on TaskManager disk | ~100 µs (disk with block cache) | Practically unlimited (disk-bound) | Large keyed state, long-window aggregation, multi-day sessions |
For AliveMCP probe analytics, the state size depends on window configuration. A 15-minute tumbling window over 5,000 MCP server slugs × per-server aggregation struct (~200 bytes) = ~1 MB keyed state — well within MemoryStateBackend limits. A 24-hour session window where each session tracks full probe-result history per server × per-server state of ~50 KB = ~250 MB — RocksDB territory.
import org.apache.flink.contrib.streaming.state.RocksDBStateBackend;
import org.apache.flink.contrib.streaming.state.EmbeddedRocksDBStateBackend;
// For Flink 1.13+ (Managed Flink uses 1.15+), use EmbeddedRocksDBStateBackend
EmbeddedRocksDBStateBackend rocksDB = new EmbeddedRocksDBStateBackend(
true // enableIncrementalCheckpointing — write only changed SST files per checkpoint
);
// RocksDB tuning for MCP probe aggregation workloads:
// - Block cache: 256 MB per TaskManager for read amplification reduction
// - Write buffer size: 64 MB for batch-write efficiency
// These are set via RocksDB options factory, not Flink API:
rocksDB.setRocksDBOptions(new MyRocksDBOptionsFactory()); // Custom OptionsFactory
env.setStateBackend(rocksDB);
Incremental checkpointing with RocksDB writes only the SST files that changed since the last checkpoint. For a 10 GB RocksDB state with 1% write rate, incremental checkpoints write ~100 MB per checkpoint instead of 10 GB — a critical difference for checkpoint overhead and S3 costs in long-running applications.
Exactly-once processing semantics with Kinesis source
Exactly-once in Flink means each input event contributes to output results exactly once, even after application restarts. This is achieved by checkpointing the Kinesis shard sequence numbers atomically with the application state — on recovery, the source replays only from the last checkpointed position, not from the beginning.
import org.apache.flink.connectors.kinesis.FlinkKinesisConsumer;
import org.apache.flink.api.common.serialization.SimpleStringSchema;
// Kinesis source configuration for exactly-once
Properties kinesisProps = new Properties();
kinesisProps.setProperty("aws.region", "us-east-1");
// Starting position on first run (no checkpoint available)
// TRIM_HORIZON: start from oldest available record
// LATEST: start from newest (lose in-flight records on first start)
// AT_TIMESTAMP: start from specific time (useful for backfill)
kinesisProps.setProperty(
ConsumerConfigConstants.STREAM_INITIAL_POSITION,
ConsumerConfigConstants.InitialPosition.TRIM_HORIZON.name()
);
// Enhanced fan-out: dedicated throughput per consumer shard (2 MB/s vs 2 MB/s shared)
// Required for high-throughput probe pipelines with multiple consumers on the same stream
kinesisProps.setProperty(
ConsumerConfigConstants.RECORD_PUBLISHER_TYPE,
ConsumerConfigConstants.RecordPublisherType.EFO.toString()
);
kinesisProps.setProperty(
ConsumerConfigConstants.EFO_CONSUMER_NAME,
"alivemcp-flink-analytics-consumer"
);
DataStream<String> probeStream = env.addSource(
new FlinkKinesisConsumer<>("alivemcp-probe-events", new SimpleStringSchema(), kinesisProps)
).uid("kinesis-source"); // Required for savepoint compatibility
At-least-once semantics (CheckpointingMode.AT_LEAST_ONCE) allows barriers to advance past in-flight records, which means the checkpoint may not include all records at the exact checkpoint boundary. On recovery, some records near the checkpoint boundary may be processed twice. For AliveMCP probe analytics producing per-server uptime percentages, this means a brief window around each checkpoint may overcount probe results. At-least-once checkpoints complete faster (30–50% lower checkpoint latency in high-throughput pipelines) and is acceptable when idempotent aggregation (e.g., deduplicating on probeId before counting) is implemented downstream.
Operator UIDs for safe savepoint migration
Every operator in a Flink job that holds state must have a stable UID assigned before you take the first savepoint. Flink generates UIDs automatically from the topology hash, but topology changes (adding an operator, reordering operators, changing operator names) change the hash and make the automatically-generated UIDs invalid for restoration. This is the most common cause of failed savepoint restores in production Managed Flink applications.
DataStream<ProbeResult> probeStream = env
.addSource(new FlinkKinesisConsumer<>(...))
.uid("kinesis-source") // Stable UID — do NOT change this after first deployment
.name("Kinesis Probe Source");
DataStream<ProbeResult> parsedStream = probeStream
.flatMap(new ProbeResultParser())
.uid("probe-result-parser")
.name("Probe Result JSON Parser");
DataStream<ServerUptimeWindow> windowedStream = parsedStream
.keyBy(ProbeResult::getServerSlug)
.window(TumblingEventTimeWindows.of(Time.minutes(15)))
.aggregate(new UptimeAggregator())
.uid("uptime-window-aggregator")
.name("15-min Uptime Window Aggregator"); // Display name can change freely; UID cannot
windowedStream
.addSink(new FlinkKinesisProducer<>(...))
.uid("kinesis-results-sink")
.name("Kinesis Output Sink");
Assignment rules:
- Assign UIDs to every stateful operator — source, window operators, and sinks with transactional state. Stateless map/filter operators do not strictly require UIDs but assigning them is safe and future-proofs topology changes.
- Never change a UID after the first savepoint is taken — even if you rename the operator or move it in the topology. The UID is the key used to match state in a savepoint to the corresponding operator in the restored application.
- Missing UIDs cause savepoint restore to fail for stateful operators — Flink cannot restore keyed state or window state for an operator without a stable UID. The restore silently drops unmatched state, causing state loss on the next deployment.
Checkpoint monitoring in CloudWatch
Managed Flink publishes checkpoint metrics to CloudWatch under the AWS/KinesisAnalytics namespace. The key metrics to alarm on:
| Metric | What it measures | Alarm threshold | Implication |
|---|---|---|---|
| lastCheckpointDuration | Time (ms) for the last completed checkpoint | > (checkpoint interval × 0.8) | Checkpoint taking too long — may trigger timeout and reset |
| lastCheckpointSize | Bytes written to S3 for the last checkpoint | Trend alert (growing checkpoint size) | State accumulation — unbounded keyed state or missing state expiry |
| numberOfFailedCheckpoints | Cumulative count of checkpoint failures | > 0 for ≥ 5 minutes | Checkpoint consistently failing — application at risk of losing recovery point |
| numberOfCompletedCheckpoints | Cumulative count of successful checkpoints | Rate drops to 0 for > 2× checkpoint interval | Checkpointing stopped — may indicate application stuck or checkpoint disabled |
| uptime | Application uptime in milliseconds | Resets to 0 | Application restarted — determine if from transient failure or checkpoint restore |
Checkpoint duration growing over time (even if below the alarm threshold) indicates unbounded state accumulation. For an AliveMCP pipeline, common causes are: keyed aggregation state without a TTL (StateTtlConfig), session windows without a maximum session gap bound, or a growing deduplication set that never expires old probeIds. Set StateTtlConfig on all keyed state descriptors with a TTL matching your data retention requirements to prevent unbounded state growth.
Complete failure mode reference
| Symptom | Root cause | Detection | Fix |
|---|---|---|---|
| Application restarts but produces duplicate results | At-least-once semantics; source replaying records near checkpoint boundary | Downstream duplicate probe results in S3/Kinesis output around restart time | Deduplicate on probeId downstream, or switch to exactly-once mode |
| Savepoint restore fails — state not found for operator | Operator UID not assigned or changed since last savepoint | Application starts but keyed state is empty after restore; CloudWatch shows 0 lastCheckpointSize immediately after start | Assign stable UIDs before next deployment; restore from checkpoint instead of savepoint as fallback |
| Checkpoint timeout (lastCheckpointDuration > timeout) | State backend write to S3 too slow; TaskManager GC pause; checkpoint barrier blocked by slow operator | numberOfFailedCheckpoints incrementing; lastCheckpointDuration approaching timeout value | Enable incremental checkpoints (RocksDB); increase checkpoint timeout; reduce state size with TTL; check for GC pressure in TaskManager logs |
| Checkpoint size growing 10% per hour | Unbounded keyed state (no StateTtlConfig or state cleared without eviction) | lastCheckpointSize trend in CloudWatch; OutOfMemory on TaskManager (MemoryStateBackend) | Add StateTtlConfig with setUpdateType(OnReadAndWrite) and setStateVisibility(NeverReturnExpiredData) |
| Application fails to start from checkpoint after upgrade | New application version changed topology without compatible UIDs; Flink version major upgrade (state format incompatible) | Application in RUNNING then immediately FAILED loop; CloudWatch uptime resets repeatedly | Take savepoint before upgrade; restore from savepoint with allow-non-restored-state flag for added operators; for major Flink version upgrades, plan full state migration |