A distributed stream processing framework that processes data event-by-event with millisecond latency, built for stateful computations over unbounded data streams.
Apache Flink is the stream-processing engine that takes streaming seriously. Unlike Spark Streaming's micro-batch model (collect events for 100ms-few-seconds, then process as a tiny batch), Flink processes each event individually as it arrives — true event-at-a-time streaming with millisecond latency. Combined with the best state management in the open-source ecosystem (RocksDB-backed keyed state scaling to TB per worker), exactly-once semantics, and mature event-time processing with watermarks, Flink is the standard choice for any non-trivial streaming workload.
Flink shines for: real-time fraud/anomaly detection, complex event processing (CEP), sessionization of user activity, stream-stream joins over time windows, real-time feature stores for ML, and any workload where latency below 1 second matters. It's overkill for: simple ETL-ish transformations where 5-minute latency is fine (use dbt or Spark batch), pure fan-out with no transformation (Kafka Streams or Connect), and teams without Java/Scala/Python expertise willing to learn Flink's conceptual model (event time, watermarks, state backends, checkpointing). Flink's power comes with genuine complexity.
An e-commerce client built real-time personalization: for every product-page view, enrich with the user's last-30-days purchase history and last-hour browsing behavior, score against a live model, and return recommendations within 200ms. The naïve Redis-based architecture fell over at 80k events/sec; every event needed a Redis lookup with network roundtrip. We rebuilt on Flink: user state (purchase history + browsing session) kept in keyed RocksDB state locally to the worker processing that user's events, no Redis roundtrip needed. End-to-end latency dropped from 450ms p99 to 65ms p99, and throughput scaled linearly to 400k events/sec on 20 workers. State size per worker reached 120GB, checkpointed every 90 seconds to S3. The rebuild took four months; the performance improvement and cost savings (no Redis cluster) paid back in six. Flink isn't free to learn, but the payoff for the right problem is enormous.
Apache Flink is a distributed processing engine for stateful computations over both bounded (batch) and unbounded (streaming) data. Unlike Spark's micro-batch approach, Flink processes each event individually as it arrives, achieving true millisecond-level latency.
┌─────────────────────────────────────────────┐
│ Flink Cluster │
│ ┌──────────┐ ┌────────────────────────┐ │
│ │JobManager│──→│ TaskManagers │ │
│ │(Master) │ │ ┌──────┐ ┌──────┐ │ │
│ │ │ │ │Slot 1│ │Slot 2│ ... │ │
│ └──────────┘ │ └──────┘ └──────┘ │ │
│ └────────────────────────┘ │
└─────────────────────────────────────────────┘| API | Level | Use Case |
|---|---|---|
| Flink SQL | Highest | SQL queries on streams |
| Table API | High | Relational operations |
| DataStream API | Low | Custom stream processing |
| ProcessFunction | Lowest | Full control over time and state |
Flink pitfalls from production deployments:
setUidHash on operators. Without stable UIDs on operators, savepoints can't map old state to new operators after a topology change. Always assign UIDs to every operator (.uid("user-aggregator")) from day one.GROUP BY user_id on an unbounded stream produces a continuously-updating changelog, not a one-shot result. Teams with SQL-only background get confused by CDC semantics and retraction streams. Read the Flink SQL CDC docs before deploying.cancel kills the job and its state; stop-with-savepoint gracefully stops and persists state for later restart. Using cancel in CI-automation has lost many TB of state.Flink is used for real-time stream-processing applications that need low latency and stateful computations. Common use cases: (1) fraud and anomaly detection with sub-second latency; (2) real-time personalization and recommendations where user state drives the decision; (3) IoT analytics processing millions of sensor events/sec; (4) streaming ETL with exactly-once semantics; (5) complex event processing (CEP) — detecting patterns like "3 login failures followed by a successful login within 5 minutes"; (6) real-time feature stores for ML; (7) stream-stream joins over time windows; (8) CDC processing from Debezium → Kafka → Flink → downstream. Essentially: any workload where you need to process events in real time with memory of prior events.
For true real-time streaming needs, yes. Flink processes events individually (event-at-a-time) with millisecond latency; Spark Structured Streaming uses micro-batches (100ms-seconds) adding inherent latency. Flink has superior state management (RocksDB scaling to TB) and event-time semantics. However, Spark wins if: (1) you're already deep in the Spark ecosystem; (2) you need unified batch + streaming in one framework; (3) your latency SLA allows seconds-scale; (4) your team knows Spark better. Both have their place. Choose Flink when latency and state scale matter; choose Spark when you prioritize ecosystem continuity with batch workloads. Many organizations use both for different purposes.
Stateful processing means Flink remembers information across events — per-user running totals, session windows, pattern-matching buffers, deduplication tables, stream-stream join buffers. Flink manages this state with: (1) keyed state partitioned by a key (user_id, device_id); (2) RocksDB backend scaling to TB of state per worker; (3) automatic incremental checkpointing to S3/HDFS; (4) exactly-once guarantees via the checkpoint-aligned algorithm; (5) savepoints for zero-downtime upgrades with state migration; (6) state TTL for automatic expiration. This is Flink's killer feature — the area where it genuinely outperforms all other open-source stream-processing engines.
Flink SQL is approachable for SQL-proficient users — you can write streaming aggregations in SQL and get reasonable results quickly. The DataStream API has a steeper learning curve: you need to understand event time vs. processing time, watermarks, windows (tumbling, sliding, session), state types (keyed vs. operator), state backends, checkpointing, savepoints, and exactly-once semantics. The ProcessFunction API (lowest level, full control) is harder still. Realistic ramp: 2 weeks for Flink SQL productivity, 2-3 months to build and debug production DataStream pipelines, 6+ months to become an effective operator. Starting with Flink SQL is strongly recommended for new adopters.
A watermark is a declaration by Flink that "all events with timestamp ≤ T have arrived; process events at or before T as complete." Watermarks handle out-of-order and late-arriving data. Without them, Flink can't decide when to close a 5-minute tumbling window — some events for the 10:00-10:05 window might still arrive at 10:07. You configure a watermark strategy (typically `forBoundedOutOfOrderness(Duration.ofSeconds(5))` meaning events may arrive up to 5 seconds late). Events arriving after the watermark are "late" and either dropped or sent to a side output. Watermarks are core to correct event-time processing and are a common source of bugs when misconfigured.
Flink provides multiple APIs at different abstraction levels. Flink SQL: write ANSI SQL against streams and tables; highest productivity, lowest flexibility. Table API: similar relational operations in Java/Scala/Python with programmatic control. DataStream API: low-level event-by-event processing with custom operators, state management, and timers — full flexibility but verbose. ProcessFunction: lowest level, full control over time, state, and side outputs. Most teams start with SQL, drop to Table/DataStream for custom logic that doesn't express well in SQL, and use ProcessFunction only for bespoke algorithms (CEP patterns, custom windows). Mixing APIs in one job is supported.
Via distributed snapshotting called checkpointing. Periodically (e.g., every 60 seconds), Flink takes a consistent snapshot of all operator state plus input source offsets (Kafka consumer positions) and writes it to durable remote storage (S3, HDFS, GCS). Checkpoints are aligned via the Chandy-Lamport algorithm so the snapshot is globally consistent. If a worker crashes, Flink automatically restarts it, restores state from the latest checkpoint, and resumes reading from the checkpointed offsets. Combined with a transactional sink (like Kafka's transactional producer), this yields end-to-end exactly-once guarantees. Savepoints are manually triggered checkpoints used for planned upgrades, scaling changes, and version migrations with zero data loss.