Blog >

Building a Real-Time Analytics Pipeline on AWS: A Practical Guide

Posted by Hadi @draft1 | October 16, 2026

Building a Real-Time Analytics Pipeline on AWS: A Practical Guide

A real-time analytics pipeline on AWS typically combines Amazon Kinesis Data Streams for ingestion, a stream processor like Kinesis Data Analytics (Managed Flink) or Lambda, and a fast-query store such as OpenSearch or DynamoDB. The goal is to move data from producers to actionable dashboards or alerts within seconds, not hours.

This matters because batch ETL — the dominant pattern for most of the last decade — is too slow for fraud detection, operational monitoring, IoT telemetry, or live personalization. Teams are increasingly asked to turn clickstreams, sensor readings, or application logs into decisions within seconds of the event occurring.

In this guide, you'll learn the core AWS services involved in a Kinesis pipeline, how to choose between the major processing options, a concrete worked example with shard math and code, and the operational trade-offs you'll hit once this runs in production.

What Does a Real-Time Analytics Pipeline Actually Look Like?

A real-time analytics pipeline is a chain of four stages: ingestion, buffering/transport, processing, and serving — each with its own AWS service choices and failure modes. Unlike batch pipelines, every stage has to handle continuous, unbounded data without a natural "end" to a job run.

On AWS, the canonical shape is:

  1. Producers — application servers, IoT devices, mobile SDKs, or database change-data-capture (CDC) via DMS or DynamoDB Streams.
  2. Ingestion/buffer — Amazon Kinesis Data Streams (or Amazon MSK for Kafka-based shops) absorbs bursts and decouples producers from consumers.
  3. Processing — Kinesis Data Analytics for Apache Flink, AWS Lambda, or Amazon Managed Service for Apache Flink (the current name for KDA-Flink) does windowed aggregation, enrichment, and filtering.
  4. Serving/storage — results land in DynamoDB for point lookups, OpenSearch for search/dashboards, Amazon Timestream for time-series queries, or S3 + Athena for near-real-time ad hoc analysis.

A common add-on is Kinesis Data Firehose, which doesn't do custom processing itself but reliably delivers (with optional Lambda transformation) from streams into S3, Redshift, OpenSearch, or third-party HTTP endpoints — useful when you need "good enough" latency (60 seconds or more) without managing a consumer application.

Choosing Between Kinesis, MSK, and Firehose

The right ingestion service depends on how much operational control you need versus how much you want AWS to manage, and whether you already have Kafka tooling in-house. There's no universally "best" choice — only the one that matches your latency, ecosystem, and ops-budget constraints.

Kinesis Data Streams is the default choice for teams that want a fully managed, pay-per-shard (or on-demand) stream with native integrations across AWS analytics services. It uses a shard-based model: each shard supports up to 1 MB/s or 1,000 records/s ingest, and 2 MB/s (shared, standard consumers) or 2 MB/s per consumer with Enhanced Fan-Out (EFO) output. Data is retained from 24 hours (default) up to 365 days (extended retention, additional cost).

Amazon MSK (Managed Streaming for Kafka) makes sense if you already run Kafka elsewhere, need exactly-once semantics with existing Kafka client libraries, or require features like compacted topics. It's more operationally heavy — you size brokers, manage partitions, and tune JVM-level configs — but gives you portability and an ecosystem of existing Kafka Connect connectors.

Kinesis Data Firehose is not a true stream you read from directly — it's a delivery service. Choose it when the destination is S3, Redshift, OpenSearch, Splunk, or an HTTP endpoint, and sub-minute latency is acceptable. It auto-scales, batches, compresses, and optionally transforms records via a Lambda function before delivery.

A table helps clarify the decision:

Service Latency Ops overhead Best for
Kinesis Data Streams Sub-second to seconds Low–Medium Custom real-time processing, fan-out to multiple consumers
Amazon MSK Sub-second to seconds Medium–High Existing Kafka workloads, exactly-once needs
Kinesis Data Firehose ~60s (configurable buffer) Very low Simple delivery into S3/Redshift/OpenSearch without custom code

In practice, many production pipelines use Kinesis Data Streams as the backbone, with Firehose subscribed to the same stream for cheap, durable archival to S3, while a Flink or Lambda consumer handles the real-time path. This "stream once, consume twice" pattern avoids re-ingesting data for different purposes.

Sizing and Scaling Kinesis Shards

Shard count is the single most important capacity decision in a Kinesis pipeline, and getting it wrong causes either ProvisionedThroughputExceededException errors or wasted spend. Each shard is a fixed-capacity unit, so sizing starts with your peak write rate in records/second and bytes/second.

The formula AWS recommends: shards needed = max(incoming data rate in KB/s / 1000, incoming records/s / 1000). For example, if you expect peak traffic of 5,000 events/sec averaging 2 KB each (10 MB/s), you need at least 10 shards for throughput (10 MB/s ÷ 1 MB/s), which comfortably covers the records/sec limit too.

You have two capacity modes:

  • Provisioned mode — you pick the shard count explicitly and pay per shard-hour (~$0.015/hour per shard in most US regions, as of AWS's published pricing) plus a per-million-PUT-payload-unit charge. You scale manually or via UpdateShardCount / Application Auto Scaling.
  • On-demand mode — Kinesis automatically scales capacity based on observed traffic over the preceding 30 days, up to 200 MB/s write and 400 MB/s read by default. It costs more per GB but removes the shard-math exercise entirely, which is attractive for spiky or unpredictable workloads.

A key trade-off: resharding is not instantaneous. Splitting or merging shards takes time (typically seconds to a couple of minutes per operation) and child shards start with no backlog, so a traffic spike that outpaces your reaction time will throttle writes until new shards become active. Teams running unpredictable workloads (e.g., flash-sale traffic, viral content) often default to on-demand mode specifically to avoid this failure mode, accepting the cost premium as insurance.

Processing: Lambda vs. Managed Flink

For stream processing logic, the real choice is between event-driven Lambda consumers for lightweight, per-record or micro-batch logic, and Managed Service for Apache Flink for stateful, windowed, SQL-like analytics at scale. Both read directly from Kinesis, but they solve different problems.

Lambda as a Kinesis consumer is triggered by an event source mapping that polls the stream (standard consumer, shared throughput) or receives push-based events via Enhanced Fan-Out. It's ideal for:

  • Per-record transformation and routing (e.g., enrich with a DynamoDB lookup, then write to another stream or table)
  • Simple filtering before expensive downstream processing (Lambda supports filter criteria on the event source mapping itself, reducing invocations and cost)
  • Workloads under a few minutes of processing time per batch, since Lambda has a 15-minute max execution limit

Its limitations: no built-in windowing/state management (you'd have to build that yourself on DynamoDB or ElastiCache), and a single shard can only be processed by one Lambda invocation at a time per consumer (unless using parallelization factor, which splits a shard's records across up to 10 concurrent Lambda invocations while preserving per-key ordering only if you design your partition keys carefully).

Managed Service for Apache Flink (the successor branding for what was Kinesis Data Analytics for Apache Flink, generally available since 2019 and rebranded in 2023) is built for genuine stream processing: tumbling/sliding/session windows, event-time semantics with watermarks, exactly-once state checkpointing to S3, and SQL or Java/Scala/Python (via PyFlink) APIs. Use it when you need:

  • Aggregations over time windows (e.g., "rolling 5-minute error rate per service")
  • Joins between multiple streams
  • Complex event pattern detection (e.g., "3 failed logins within 1 minute")
  • Guaranteed exactly-once processing semantics with large, recoverable state

The operational cost is real: Flink applications are billed by Kinesis Processing Unit (KPU), each bundling 1 vCPU and 4 GB memory, and you need to understand checkpointing, parallelism, and backpressure to run it reliably. It's a genuine distributed system, not a function-as-a-service abstraction.

Dimension Lambda consumer Managed Flink
State/windowing Manual (external store) Native, built-in
Max processing time 15 min per invocation Long-running, continuous
Scaling unit Concurrency / shard Parallelism / KPU
Learning curve Low Moderate–High
Best fit Routing, enrichment, filtering Windowed aggregation, joins, CEP

A Worked Example: Clickstream Analytics Pipeline

Consider an e-commerce site that wants a live "active users and conversion rate per product category" dashboard refreshed every 10 seconds, plus fraud alerts on suspicious checkout patterns.

Architecture:

  1. Client-side JS SDK sends click and purchase events to API Gateway (HTTP API), which invokes a lightweight Lambda that does PutRecord/PutRecords into a Kinesis Data Stream (clickstream-raw), partitioned by session_id.
  2. The stream runs in on-demand mode initially (traffic is unpredictable at launch), with a plan to switch to provisioned mode plus Application Auto Scaling once traffic patterns stabilize, based on CloudWatch IncomingBytes and IncomingRecords metrics.
  3. A Managed Flink application consumes the stream with Enhanced Fan-Out (dedicated 2 MB/s throughput, avoiding contention with other consumers), computes 10-second tumbling windows aggregating active_sessions and conversions per category, and writes results to DynamoDB (for the dashboard's low-latency reads via API Gateway + Lambda) and to a second Kinesis stream, clickstream-alerts.
  4. A separate Lambda subscribes to clickstream-alerts with a filter criteria (checkout_attempts > 3 AND distinct_cards > 1) and, on match, publishes to an SNS topic that pages the fraud team and writes an event to EventBridge for downstream case management.
  5. In parallel, Kinesis Data Firehose subscribes to clickstream-raw and batches records every 5 minutes or 5 MB (whichever comes first) into an S3 data lake partitioned by year/month/day/hour, queryable later via Athena or loaded into Redshift for historical analysis.

A minimal producer snippet (Python, boto3) illustrates the ingestion side:

import boto3, json, time

kinesis = boto3.client("kinesis", region_name="us-east-1")

def put_event(event: dict, session_id: str):
    kinesis.put_record(
        StreamName="clickstream-raw",
        Data=json.dumps(event).encode("utf-8"),
        PartitionKey=session_id  # ensures ordering per session
    )

put_event({
    "event_type": "purchase",
    "category": "electronics",
    "amount": 129.99,
    "ts": int(time.time())
}, session_id="sess-8841")

Partition key choice matters here: using session_id keeps a given user's events in order (within a shard) and spreads load across shards reasonably if session IDs are high-cardinality and roughly uniform. A naive choice like category (low cardinality, a handful of values) would create hot shards, since Kinesis maps partition keys to shards via consistent hashing and a few overloaded keys can saturate a single shard's 1 MB/s limit even while the stream overall has spare capacity.

Serving Layer Choices

The serving layer determines query latency and cost at read time, and the right choice depends on access pattern: point lookups, full-text/aggregation search, or time-series queries. Picking one store to serve every use case usually ends in compromises.

  • DynamoDB — single-digit millisecond point reads by partition key, ideal for "give me the latest counter for category X." Supports DynamoDB Streams for further downstream reaction. Poor fit for ad hoc aggregation across many items.
  • OpenSearch Service — strong for full-text search, faceted dashboards (often paired with OpenSearch Dashboards or Kibana-style visualization), and near-real-time (typically 1-second refresh) indexing. Higher operational cost and cluster management overhead than DynamoDB.
  • Amazon Timestream — purpose-built for time-series data (IoT sensor readings, metrics) with automatic tiering between in-memory and magnetic storage, and SQL-like queries with time-bucketing functions built in.
  • S3 + Athena (or Redshift Spectrum) — cheapest for large-scale historical queries but not "real-time" in the sub-second sense; good for the batch/historical tail of a Lambda-architecture-style pipeline.

Many production systems adopt a Lambda Architecture or Kappa Architecture hybrid here: real-time path serves the last few minutes/hours of data from DynamoDB or Timestream, while S3/Athena/Redshift serves anything older, reconciling via a nightly or hourly batch job.

Operational Pitfalls Worth Planning For

Even a well-designed pipeline fails in predictable ways in production, and most of these failure modes are avoidable with upfront design decisions rather than reactive firefighting.

  • Hot shards from skewed partition keys — monitor WriteProvisionedThroughputExceeded per shard (not just stream-level aggregate) and redesign partition keys before scaling blindly.
  • Iterator age creep — rising GetRecords.IteratorAgeMilliseconds means consumers can't keep up; this is the primary lag indicator for Kinesis, analogous to Kafka consumer lag.
  • Lambda cold starts and concurrency limits — default account concurrency limits can throttle Kinesis-triggered Lambdas during traffic spikes; request limit increases proactively, not after an incident.
  • Checkpointing failures in Flink — misconfigured checkpoint intervals or undersized state backends cause reprocessing storms after a restart; always test failure/restart behavior in staging with production-scale state.
  • Firehose buffering masking as "real-time" — teams sometimes forget Firehose's buffer interval means data isn't queryable in S3 for up to several minutes; don't promise sub-minute SLAs on that path.

Key Takeaways

  • A real-time analytics pipeline on AWS generally combines Kinesis Data Streams (ingestion), a processor (Lambda or Managed Flink), and a serving store (DynamoDB, OpenSearch, or Timestream).
  • Choose Kinesis Data Streams for custom, low-latency processing with multiple consumers; choose Firehose for simple delivery into S3/Redshift without writing consumer code; choose MSK if you need Kafka compatibility.
  • Shard sizing is based on both throughput (1 MB/s write per shard) and record rate (1,000 records/s per shard) — size for peak, not average, and consider on-demand mode for unpredictable traffic.
  • Lambda consumers suit lightweight per-record logic; Managed Service for Apache Flink is needed for true windowed aggregation, joins, and exactly-once stateful processing.
  • Partition key design directly determines shard load distribution — low-cardinality keys cause hot shards regardless of overall stream capacity.
  • Monitor IteratorAgeMilliseconds as your primary lag signal, and treat Firehose's buffering delay as a hard constraint when setting latency SLAs.
  • Most production systems blend real-time and batch paths (a Lambda/Kappa architecture hybrid), rather than trying to serve every query from a single real-time store.

Frequently Asked Questions

What's the difference between Kinesis Data Streams and Kinesis Data Firehose?

Kinesis Data Streams is a low-latency, custom-consumer stream you read from with your own application code (Lambda, Flink, KCL apps), while Firehose is a managed delivery service that batches and writes data to destinations like S3 or Redshift without you writing consumer code. Use Streams when you need sub-second processing and multiple independent consumers; use Firehose when "good enough" latency (around a minute) and zero-ops delivery matter more.

How many shards do I need for my Kinesis stream?

Calculate shards as the larger of (peak bytes/sec ÷ 1 MB/s) and (peak records/sec ÷ 1,000), then add headroom for traffic spikes. If your traffic is unpredictable or highly variable, on-demand mode avoids this calculation entirely, at a higher per-GB cost.

Should I use Lambda or Managed Service for Apache Flink for stream processing?

Use Lambda for stateless, per-record transformation, filtering, or routing logic that completes well within 15 minutes. Use Managed Service for Apache Flink when you need windowed aggregation, multi-stream joins, event-time semantics, or exactly-once stateful processing — Lambda doesn't natively support any of these.

What causes "hot shards" in a Kinesis pipeline?

Hot shards happen when a small number of partition key values receive disproportionately high traffic, because Kinesis maps keys to shards via consistent hashing. The fix is choosing a higher-cardinality, more evenly distributed partition key (such as a session or user ID) rather than a low-cardinality attribute like category or region.

How do I monitor whether my Kinesis consumers are keeping up?

Watch the GetRecords.IteratorAgeMilliseconds CloudWatch metric, which measures how far behind the latest record your consumer currently is. A rising iterator age indicates the consumer can't process records as fast as they arrive, and you need either more consumer capacity (Lambda concurrency, Flink parallelism) or more shards.

Is Amazon MSK a better choice than Kinesis for real-time analytics?

Not universally — MSK makes sense if you already run Kafka, need Kafka-specific features like compacted topics, or require client-library portability across clouds. Kinesis is simpler to operate and integrates more natively with other AWS services (Firehose, Lambda, Flink) without broker management.

Does Kinesis Data Streams guarantee exactly-once processing?

No, Kinesis itself provides at-least-once delivery; exactly-once semantics must be implemented at the consumer layer, for example via idempotent writes in Lambda or Flink's checkpointing combined with idempotent sinks. Design your downstream writes (e.g., DynamoDB conditional writes) to tolerate duplicate record delivery.


Draw this in seconds with draft1. Describe your architecture in plain English and draft1 generates an editable AWS/cloud diagram plus documentation — no dragging boxes around. Try it free.

Draw this in ~20 seconds

Describe your own version of this architecture and draft1 generates an editable draw.io diagram — boxes, arrows, labels, the lot.

Generate this diagram free ➔

Free demo — no signup. Then 3 free diagrams with an account, no card.