100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
Programming

Building a Real-Time Analytics Pipeline

A practical walkthrough of building a Kafka-to-Flink-to-sink pipeline that powers a live analytics dashboard.

PracticeIntermediate11 min readJul 10, 2026
Analogies

Building a Real-Time Analytics Pipeline

A typical real-time analytics pipeline ingests events from Kafka, processes them with Flink to compute windowed aggregations and enrichments, and writes results to a low-latency sink such as Elasticsearch, PostgreSQL, or a key-value store powering a live dashboard. This end-to-end path introduces decisions at every stage: how to deserialize and partition events, how to window and enrich them, and how to guarantee results land in the sink exactly once even after a failure.

🏏

Cricket analogy: Building a Kafka-to-Flink-to-sink pipeline for live dashboards is like setting up a ball-by-ball scoring system at a stadium — raw deliveries stream in, get processed instantly, and update the scoreboard for fans in real time.

Ingesting Events from Kafka

Configure a KafkaSource with an explicit deserialization schema (e.g. a JSON or Avro DeserializationSchema) rather than raw bytes, so malformed messages can be caught and routed to a dead-letter side output instead of crashing the job. Choose the Kafka partition key so it aligns with the keyBy() you'll use downstream — partitioning by userId or deviceId in Kafka and then keying by the same field in Flink keeps related events co-located, minimizing network shuffles during the windowed aggregation stage.

🏏

Cricket analogy: Configuring a KafkaSource with the right deserialization schema is like a scorer correctly reading each ball-by-ball commentary feed format before entering it into the system — get the format wrong and every subsequent run tally is corrupted.

Windowed Aggregation and Enrichment

Use TumblingEventTimeWindows for fixed-period metrics like requests-per-minute, or SlidingEventTimeWindows when you need a continuously updating rolling metric like a trailing 5-minute average. Enrich each event with reference data — user profile, product catalog, exchange rate — using an interval join against a second stream, which matches events falling within a bounded time range of each other, or a broadcast state pattern for slowly-changing lookup data. For custom logic that doesn't fit standard windows, such as detecting a gap of inactivity per key, use a KeyedProcessFunction with registered event-time timers.

🏏

Cricket analogy: A tumbling window computing runs-per-over is like a scorer resetting the tally at the start of every new over, while an interval join enriching ball events with player stats resembles cross-referencing the batter's season average the moment they face a delivery.

java
DataStream<PageView> views = env.fromSource(
    KafkaSource.<PageView>builder()
        .setBootstrapServers("broker:9092")
        .setTopics("page-views")
        .setDeserializer(new PageViewDeserializationSchema())
        .setStartingOffsets(OffsetsInitializer.latest())
        .build(),
    WatermarkStrategy.<PageView>forBoundedOutOfOrderness(Duration.ofSeconds(5))
        .withTimestampAssigner((e, ts) -> e.getEventTime()),
    "page-views-source");

DataStream<PageViewCount> perMinuteCounts = views
    .keyBy(PageView::getPageId)
    .window(TumblingEventTimeWindows.of(Time.minutes(1)))
    .aggregate(new CountAggregate(), new AttachWindowMetadata());

perMinuteCounts.sinkTo(elasticsearchSink);

Sinking Results and Ensuring Exactly-Once Delivery

For sinks that support transactions, such as Kafka or a JDBC-compatible database via Flink's two-phase commit protocol (TwoPhaseCommitSinkFunction or the newer Sink V2 with WithPreCommitTopology), Flink coordinates the sink's commit with checkpoint completion so results are written exactly once even across job restarts. When the sink doesn't support transactions, achieve effectively-once delivery through idempotent writes — for example, a JDBC upsert (INSERT ... ON CONFLICT DO UPDATE) keyed by a deterministic ID derived from the window and key, so a retried write after a failure simply overwrites the same row instead of duplicating it.

🏏

Cricket analogy: A two-phase commit sink writing final scores is like an official scorer only certifying the scorecard once both the third umpire and match referee confirm the result — nothing is finalized until every party commits.

Prefer Flink's Sink V2 API (implementing StatefulSink and TwoPhaseCommittingSink) for new connectors over the legacy SinkFunction interface. It integrates more cleanly with checkpointing and unaligned checkpoints.

Do not assume a plain JDBC INSERT sink is exactly-once by default. Without an idempotent key or a two-phase commit wrapper, a job restart after a checkpoint failure can replay records and write duplicate rows into the sink table.

  • Use an explicit deserialization schema on your KafkaSource and route malformed records to a dead-letter side output.
  • Align Kafka partition keys with your Flink keyBy() field to minimize network shuffles.
  • Use tumbling windows for fixed-period metrics and sliding windows for continuously updating rolling metrics.
  • Enrich streams with interval joins for time-bounded matches or broadcast state for slowly-changing reference data.
  • Use KeyedProcessFunction with event-time timers for custom logic like inactivity detection.
  • Prefer transactional two-phase commit sinks, or idempotent upserts, to guarantee exactly-once (or effectively-once) delivery.
  • Never assume a plain sink is exactly-once without an explicit idempotency or transaction mechanism.

Practice what you learned

Was this page helpful?

Topics covered

#Programming#ApacheFlinkStudyNotes#BuildingARealTimeAnalyticsPipeline#Building#Real#Time#Analytics#DevOps#StudyNotes#SkillVeris