Apache Flink

Apache Flink is the most widely used open-source engine for stateful stream processing. This page explains its architecture, its two APIs, how it manages time and state, and how checkpoints give exactly-once results.


1. What Apache Flink Is

Apache Flink is an open-source engine for stateful computation over unbounded and bounded data streams. "Unbounded" means the input never ends — a Kafka topic of clicks, payments or sensor readings. Flink processes each event as it arrives, keeps whatever memory the computation needs (counts, windows, joins in progress) as managed state, and periodically snapshots that state so it can recover after a crash without losing or double-counting results.

Flink Cluster Architecture Client submits JobGraph JobManager Dispatcher REST, job submit JobMaster schedules tasks Checkpoint Coord. triggers barriers TaskManager 1 slot 1 task slot 2 task slot 3 task TaskManager 2 slot 1 task slot 2 task slot 3 task Data flows TaskManager to TaskManager over the network; state lives inside each task

A Flink cluster has a JobManager that accepts jobs, turns them into a graph of parallel tasks, schedules those tasks and coordinates checkpoints, and one or more TaskManagers, worker JVMs that each offer a number of task slots. Each slot runs one parallel slice of the pipeline. Records move between slots over the network with credit-based flow control, so a slow operator naturally slows its upstream (backpressure) rather than running out of memory.

2. Why Flink Matters

Batch pipelines answer questions hours later. Many problems need answers within seconds: fraud scoring on a card swipe, alerting on a failing sensor, updating a live dashboard, or keeping a search index in sync with a database. Flink is built for these cases. Its distinguishing features are true event-at-a-time processing (rather than micro-batches), first-class event time with watermarks so late and out-of-order events are handled correctly, large keyed state that can spill to local disk via RocksDB, and exactly-once state consistency through distributed snapshots. With transactional sinks, that guarantee can extend end-to-end.

Flink also treats batch as a special case of streaming: a bounded input is simply a stream that ends. The same SQL query or DataStream program can backfill a year of history from files and then continue on live Kafka data, which removes the need to maintain separate batch and streaming code paths for the same metric. Common production uses include real-time ETL into lakehouse tables, continuous aggregation for dashboards, complex event processing (detecting patterns such as three failed logins followed by a password reset), and enrichment joins between a fast event stream and a slowly changing reference table.

3. DataStream and Table/SQL APIs

A Keyed Streaming Job Kafka source orders topic map / filter parse JSON keyBy(user) hash partition window #1 users A-M window #2 users N-Z Sink Postgres / Kafka keyed state RocksDB / heap Each parallel window instance owns the state for its keys

The DataStream API (Java, with a Python version in PyFlink) gives full control: sources, transformations such as map and filter, keyBy to partition by key, windows, timers and process functions with explicit state. The diagram shows a typical shape: parse events, partition by user so that all of a user's events reach the same parallel instance, aggregate in windows, then write results.

The Table API and Flink SQL sit above it. You declare tables over Kafka topics or files and write standard SQL; the planner turns it into the same kind of operator graph. A GROUP BY on a stream produces a continuously updating result (a changelog), and windowing is expressed with table-valued functions like TUMBLE and HOP. Most new pipelines start in SQL and drop to DataStream only for custom logic.

Time and watermarks. Event time is when something happened; processing time is when Flink sees it. A watermark with timestamp t asserts "no more events older than t are expected." A window [12:00, 12:05) fires when the watermark passes 12:05. Watermarks are usually generated as max_event_time − allowed_delay; a larger delay tolerates more disorder at the cost of latency.

4. State and Checkpoints

Checkpoint Barriers (Chandy-Lamport style) stream e e e e e e e barrier n operator snapshots state durable storage S3 / HDFS 1. JobManager injects barrier n at every source 2. Operator aligns barriers from all inputs 3. Operator writes state snapshot, forwards barrier 4. All acks received: checkpoint n complete On failure: restore last checkpoint, rewind sources

Flink's fault tolerance is based on the Chandy-Lamport distributed snapshot algorithm, adapted as asynchronous barrier snapshotting. The JobManager periodically injects a numbered barrier into every source. Barriers travel with the data. When an operator has received barrier n on all its inputs, its state reflects exactly the events before the barrier, so it snapshots that state to durable storage and passes the barrier on. When every operator has acknowledged, checkpoint n is complete, and the source offsets recorded with it mark where to resume.

After a failure, Flink restarts the job, restores every operator's state from the last completed checkpoint, and rewinds sources such as Kafka to the stored offsets. Events after the checkpoint are replayed, and because state was rolled back to match, each event affects state exactly once. Savepoints are the same mechanism triggered manually, used for upgrades, rescaling and migrations. State backends are either heap memory (fast, limited by RAM) or embedded RocksDB (larger than memory, supports incremental checkpoints).

5. Worked Example: Flink SQL

This job reads JSON orders from Kafka, computes revenue per user per one-minute tumbling window in event time, and writes results to another topic. Run it in the Flink SQL client with the Kafka connector JAR on the classpath.

CREATE TABLE orders (
  order_id   STRING,
  user_id    STRING,
  amount     DECIMAL(10, 2),
  order_time TIMESTAMP(3),
  WATERMARK FOR order_time AS order_time - INTERVAL '10' SECOND
) WITH (
  'connector' = 'kafka',
  'topic' = 'orders',
  'properties.bootstrap.servers' = 'localhost:9092',
  'properties.group.id' = 'flink-revenue',
  'scan.startup.mode' = 'earliest-offset',
  'format' = 'json'
);

CREATE TABLE revenue_per_minute (
  user_id      STRING,
  window_start TIMESTAMP(3),
  window_end   TIMESTAMP(3),
  revenue      DECIMAL(12, 2),
  orders       BIGINT
) WITH (
  'connector' = 'kafka',
  'topic' = 'revenue_per_minute',
  'properties.bootstrap.servers' = 'localhost:9092',
  'format' = 'json'
);

SET 'execution.checkpointing.interval' = '30s';

INSERT INTO revenue_per_minute
SELECT user_id, window_start, window_end, SUM(amount), COUNT(*)
FROM TABLE(TUMBLE(TABLE orders, DESCRIPTOR(order_time), INTERVAL '1' MINUTE))
GROUP BY user_id, window_start, window_end;

The WATERMARK clause makes order_time the event-time attribute and tolerates ten seconds of disorder. Each window emits once, when the watermark passes its end, so the output is append-only. The equivalent in PyFlink's DataStream API would use key_by(lambda o: o.user_id), window(TumblingEventTimeWindows.of(Time.minutes(1))) and a reduce function.

6. Flink vs Alternatives

EngineModelStateTypical latencyBest fit
Apache FlinkEvent-at-a-time, event time nativeLarge keyed state, RocksDB, exactly-onceMillisecondsComplex stateful streaming, CEP, streaming SQL
Spark Structured StreamingMicro-batch (default)State store, exactly-onceSub-second to secondsTeams already on Spark, unified batch and stream
Kafka StreamsLibrary inside your appLocal RocksDB backed by Kafka changelogsMillisecondsKafka-to-Kafka microservices, no cluster to run
Managed (Kinesis Data Analytics / Amazon Managed Service for Apache Flink)Hosted FlinkSame as FlinkMillisecondsFlink without cluster operations

7. Pitfalls

9. Further Reading