Home DevOps Amazon Kinesis: Real-Time Data Streaming and Analytics – Production Patterns That Don't Fall Over
Advanced 3 min · July 18, 2026

Amazon Kinesis: Real-Time Data Streaming and Analytics – Production Patterns That Don't Fall Over

Amazon Kinesis real-time streaming production patterns: shard scaling, error handling, and cost traps from 15 years of AWS data pipelines..

N
Naren Founder & Principal Engineer

20+ years shipping production infrastructure and CI/CD at scale. Drawn from code that ran under real load.

Follow
Production
production tested
July 18, 2026
last updated
2,466
articles · all by Naren
Before you start⏱ 35 min
  • AWS account with Kinesis access
  • Basic understanding of stream processing (Kafka, Pub/Sub)
  • Familiarity with IAM roles and CloudWatch metrics
 ● Production Incident 🔎 Debug Guide ⚙ Triage Commands
Quick Answer

Use Kinesis Data Streams when you need low-latency, multi-consumer access to streaming data with replay capability. Use Kinesis Data Firehose when you want a simple, auto-scaling delivery to S3/Redshift without managing consumers. Use Kinesis Data Analytics when you need real-time SQL queries on the stream.

✦ Definition~90s read
What is Amazon Kinesis?

Amazon Kinesis is a managed real-time data streaming service that ingests and processes large streams of data records in near real-time. It includes Kinesis Data Streams for raw ingestion, Kinesis Data Firehose for delivery to S3/Redshift, and Kinesis Data Analytics for SQL-based stream processing.

Think of Kinesis as a conveyor belt in a busy warehouse.
Plain-English First

Think of Kinesis as a conveyor belt in a busy warehouse. Items (data records) are placed on the belt at one end. Multiple workers can pick items off the belt at different points, each doing their own job—one counts items, another checks for defects. The belt is divided into lanes (shards), each with a fixed capacity. If you need more throughput, you add more lanes. The belt remembers every item for up to 7 days, so a worker can rewind and re-check if needed.

⚙ Browser compatibility
Latest versions — ✓ supported
ChromeFirefoxSafariEdge

You're running a real-time analytics pipeline. Data flows in from thousands of devices, and you need to process it, enrich it, and land it in S3 within seconds. You set up Kinesis, write a Lambda consumer, and it works in dev. Then production hits. Your Lambda starts throttling, shards are hot, and your bill explodes. Sound familiar? Kinesis is deceptively simple, but the defaults will burn you at scale.

The problem Kinesis solves is ingesting and processing high-throughput streaming data without building your own Kafka cluster. It handles durability, replication, and automatic failover. But the trade-off is a fixed shard model that requires careful capacity planning and consumer design.

By the end of this article, you'll be able to design a Kinesis pipeline that handles 10x your expected load without dropping records, diagnose shard hot-spotting in minutes, and avoid the cost traps that catch 90% of teams.

Shard Calculus: How Many Shards Do You Actually Need?

Every Kinesis shard provides 1 MB/s write and 2 MB/s read throughput. That's it. No bursting, no credits. If you exceed 1 MB/s on a shard for more than a few seconds, you get WriteProvisionedThroughputExceeded. The classic mistake is sizing shards based on average throughput, not peak. If your average is 5 MB/s but your peak is 20 MB/s for 10 seconds, you need 20 shards, not 5. Use the formula: shards = ceil(peak_write_throughput_MBps / 1.0). But also consider partition key distribution. A single hot partition key can max out one shard while others sit idle. Always use a high-cardinality partition key (like a UUID or a hash of the record). Never use timestamp alone—that creates a hot shard every second.

shard_calculator.pyPYTHON
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# io.thecodeforge — DevOps tutorial

import math

def calculate_shards(peak_mbps: float, avg_record_size_kb: float, records_per_second: int) -> int:
    """
    Calculate minimum shards for Kinesis Data Streams.
    Shard write limit: 1 MB/s or 1000 records/s, whichever is hit first.
    """
    throughput_shards = math.ceil(peak_mbps / 1.0)
    # 1000 records/s per shard
    record_rate_shards = math.ceil(records_per_second / 1000)
    return max(throughput_shards, record_rate_shards)

# Example: 10 MB/s peak, 2 KB avg record, 5000 records/s
shards = calculate_shards(10.0, 2, 5000)
print(f"Minimum shards: {shards}")  # Output: Minimum shards: 10
Output
Minimum shards: 10
⚠ Production Trap:
Never use a monotonically increasing partition key (like timestamp). It creates a hot shard because all records go to the same shard. Always hash or use a UUID.

Producer Patterns: Reliable Ingestion Without Data Loss

The Kinesis Producer Library (KPL) is the recommended way to send data. It aggregates records into larger payloads (up to 1 MB) and uses a background thread to send them. But KPL has a gotcha: it buffers records in memory and on disk. If your process crashes before the buffer is flushed, you lose data. For critical pipelines, use the AWS SDK directly with synchronous putRecord calls and a retry queue. The KPL's RecordMaxBufferedTime default is 100 ms—too low for high throughput. Set it to 1000 ms to reduce API calls. Also, always enable AggregationEnabled (default true) to pack multiple records into one PutRecords request. For Firehose, the producer is simpler: just PutRecord to the delivery stream. Firehose automatically buffers and batches to S3/Redshift. But watch out: Firehose has a 60-second buffer interval by default. If you need lower latency, reduce it to 60 seconds minimum (cannot go lower).

kinesis_producer.pyPYTHON
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# io.thecodeforge — DevOps tutorial

import boto3
import json
from botocore.config import Config

# Production config: retries with backoff, timeouts
config = Config(
    retries={'max_attempts': 5, 'mode': 'adaptive'},
    read_timeout=10,
    connect_timeout=5
)
kinesis = boto3.client('kinesis', config=config)

def send_event(stream_name: str, partition_key: str, event: dict) -> bool:
    """Send a single event with retry. Returns True on success."""
    try:
        response = kinesis.put_record(
            StreamName=stream_name,
            Data=json.dumps(event).encode('utf-8'),
            PartitionKey=partition_key
        )
        # Check for throttling
        if response['ResponseMetadata']['HTTPStatusCode'] == 200:
            return True
        else:
            print(f"Non-200 response: {response}")
            return False
    except Exception as e:
        print(f"PutRecord failed: {e}")
        return False

# Usage
send_event('my-stream', 'user-123', {'action': 'click', 'timestamp': 1234567890})
Output
True
💡Senior Shortcut:
Use PutRecords (batch) instead of putRecord for high throughput. It sends up to 500 records in one call. But handle partial failures: check the FailedRecordCount in the response and retry only the failed records.

Consumer Design: Lambda vs. KCL vs. Firehose

You have three main consumer options: Lambda, Kinesis Client Library (KCL), and Firehose. Lambda is the simplest: it polls each shard and invokes your function with a batch of records. But Lambda has a concurrency limit (default 1000 per account) and each shard can only be processed by one Lambda invocation at a time. If your processing takes longer than the iterator age, you fall behind. The fix: increase Lambda batch size and concurrency, but also ensure your function is idempotent because Lambda may retry on failure. KCL (Java/KCL multi-lang daemon) gives you more control: it manages checkpointing, shard discovery, and load balancing across workers. Use KCL when you need exactly-once semantics or custom checkpointing. Firehose is the simplest: it reads from a stream and writes to S3/Redshift/Elasticsearch. No code needed. But you lose the ability to transform data in-flight (unless you use Lambda transformation, which adds latency).

lambda_consumer.pyPYTHON
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# io.thecodeforge — DevOps tutorial

import base64
import json

def lambda_handler(event, context):
    """Process Kinesis records from a Lambda trigger."""
    for record in event['Records']:
        # Decode base64 data
        payload = base64.b64decode(record['kinesis']['data']).decode('utf-8')
        data = json.loads(payload)
        
        # Process the record (idempotent)
        process_event(data)
        
        # If processing fails, raise exception to trigger retry
        # Lambda will retry the entire batch unless you catch and skip
    return {'batchItemFailures': []}  # Return failed sequence numbers for partial success

def process_event(data):
    # Your business logic here
    pass
🔥Interview Gold:
Lambda processes each shard sequentially. If one record fails, the entire batch is retried until success or expiry. Use bisectBatchOnFunctionError to retry smaller batches and isolate bad records.

Error Handling and Retries: The 3 AM Wake-Up Call

Kinesis retries are brutal by default. If a Lambda consumer throws an error, Lambda retries the entire batch until the data expires (up to 7 days). This can cause a backlog that takes hours to clear. The fix: implement dead-letter queues (DLQ) for poison pills. Use a Lambda destination on failure to send the failed record to an SQS queue. Then process the DLQ separately. For KCL, handle ShutdownException and ThrottlingException by checkpointing only after successful processing. Never checkpoint before processing—if your worker crashes, you lose records. For Firehose, failed deliveries are retried for up to 24 hours, then sent to a backup S3 bucket. Always configure a backup bucket.

dlq_handler.pyPYTHON
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# io.thecodeforge — DevOps tutorial

import json
import boto3

sqs = boto3.client('sqs')
DLQ_URL = 'https://sqs.us-east-1.amazonaws.com/123456789012/kinesis-dlq'

def send_to_dlq(failed_record: dict, error: str):
    """Send a failed Kinesis record to SQS DLQ."""
    message = {
        'record': failed_record,
        'error': error,
        'timestamp': int(time.time())
    }
    sqs.send_message(
        QueueUrl=DLQ_URL,
        MessageBody=json.dumps(message)
    )

def lambda_handler(event, context):
    failed_sequences = []
    for record in event['Records']:
        try:
            process_record(record)
        except Exception as e:
            # Send to DLQ and skip (don't retry)
            send_to_dlq(record, str(e))
            failed_sequences.append(record['kinesis']['sequenceNumber'])
    return {'batchItemFailures': [{'itemIdentifier': seq} for seq in failed_sequences]}
⚠ Never Do This:
Don't use try-except with pass to swallow errors. You'll lose data silently. Always log and send to a DLQ.

Monitoring and Alerting: The Metrics That Matter

CloudWatch emits several Kinesis metrics. The critical ones: WriteProvisionedThroughputExceeded (shard throttling), ReadProvisionedThroughputExceeded (consumer throttling), MillisBehindLatest (consumer lag), and UserRecords.Success (ingestion rate). Set CloudWatch alarms on MillisBehindLatest > 5000 ms for 5 minutes. Also monitor GetRecords.IteratorAgeMilliseconds for Lambda consumers. For Firehose, monitor DeliveryToS3.Success and BackupToS3.Success. A common mistake is not setting up dashboards. Create a single pane of glass with shard count, throughput, iterator age, and error rates. Use Contributor Insights to find hot partition keys.

cloudwatch_alarm.jsonJSON
1
2
3
4
5
6
7
8
9
10
11
12
13
// io.thecodeforge — DevOps tutorial

{
  "AlarmName": "Kinesis-IteratorAge-High",
  "MetricName": "MillisBehindLatest",
  "Namespace": "AWS/Kinesis",
  "Statistic": "Maximum",
  "Period": 300,
  "EvaluationPeriods": 2,
  "Threshold": 5000,
  "ComparisonOperator": "GreaterThanThreshold",
  "AlarmActions": ["arn:aws:sns:us-east-1:123456789012:alert-topic"]
}
💡Senior Shortcut:
Use CloudWatch Contributor Insights to identify hot partition keys. The rule { "key": [ "$.partitionKey" ] } will show which keys are causing throttling.

Cost Optimization: Don't Let Shards Drain Your Wallet

Kinesis Data Streams charges per shard-hour ($0.015 per shard-hour in us-east-1). A 100-shard stream costs $1.50/hour = $1,080/month. That's before data transfer and API costs. The biggest cost trap: over-provisioning shards for peak throughput and leaving them running 24/7. Use auto-scaling: monitor WriteProvisionedThroughputExceeded and scale up during peaks, scale down during lulls. AWS provides a shard-level CloudWatch metric IncomingBytes and IncomingRecords. Use Application Auto Scaling with a target tracking policy on IncomingBytes (target 0.8 MB/s per shard). For Firehose, costs are based on data volume ($0.029/GB) and S3 storage. Firehose is cheaper than Kinesis Streams for simple delivery. For Data Analytics, costs are per Kinesis Processing Unit (KPU) at $0.11/hour. Optimize Flink jobs by tuning parallelism and state backend.

auto_scaling_policy.jsonJSON
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// io.thecodeforge — DevOps tutorial

{
  "PolicyName": "kinesis-shard-scaling",
  "ServiceNamespace": "kinesis",
  "ResourceId": "stream/my-stream",
  "ScalableDimension": "kinesis:stream:WriteCapacityUnits",
  "TargetTrackingScalingPolicyConfiguration": {
    "TargetValue": 0.8,
    "PredefinedMetricSpecification": {
      "PredefinedMetricType": "KinesisWriteProvisionedThroughputExceeded"
    },
    "ScaleOutCooldown": 60,
    "ScaleInCooldown": 300
  }
}
🔥Production Trap:
Auto-scaling can't scale down below 1 shard. If your stream is idle, you're still paying for 1 shard. Consider using a Lambda to delete the stream during off-hours and recreate it.

When Not to Use Kinesis: The Alternatives

Kinesis is not a message queue. If you need individual message delivery, exactly-once semantics, or fan-out to many consumers, use SQS or SNS. Kinesis is a stream: all consumers read the same data in order. If you need to broadcast to multiple independent consumers, each with their own offset, Kinesis works (each consumer group gets its own shard iterator), but Kafka is cheaper for high-throughput fan-out. For very low latency (< 100 ms), consider AWS IoT Core or WebSocket APIs. For batch processing, use S3 events with Lambda. Kinesis is overkill if your throughput is < 1 MB/s and you don't need replay. A simple Lambda with SQS might be cheaper and simpler.

💡Senior Shortcut:
If you need exactly-once delivery to a single destination, use Firehose with S3. If you need multi-consumer with replay, use Kinesis Streams. If you need a queue, use SQS.
● Production incidentPOST-MORTEMseverity: high

The 4GB Container That Kept Dying

Symptom
A Kinesis Data Analytics Flink application kept restarting with OutOfMemoryError: Java heap space every 30 minutes.
Assumption
The team assumed the application needed more memory and increased the Parallelism and TaskManager memory to 8GB.
Root cause
The application was using KeyedStream.keyBy() on a high-cardinality field (user_id), causing Flink to create millions of internal key states. The default RocksDB state backend was flushing too aggressively, causing memory pressure and eventual OOM.
Fix
Switched to FlinkKinesisConsumer with SHARD_USE_SEQUENCE_NUMBER and reduced key cardinality by pre-aggregating on a lower-cardinality field (e.g., geo_region). Set state.backend.rocksdb.memory.managed: true and taskmanager.memory.process.size: 4g.
Key lesson
  • Always profile key cardinality before using keyBy in Flink.
  • High cardinality kills state backends.
Production debug guideSystematic recovery paths for the failure modes engineers actually hit.3 entries
Symptom · 01
WriteProvisionedThroughputExceeded errors in producer logs
Fix
1. Check CloudWatch WriteProvisionedThroughputExceeded metric. 2. Identify hot partition key using Contributor Insights. 3. Increase shard count or re-partition with better key distribution. 4. Implement exponential backoff in producer.
Symptom · 02
MillisBehindLatest > 10 seconds
Fix
1. Check consumer processing time per record. 2. Increase Lambda batch size and concurrency. 3. If using KCL, add more workers. 4. Consider increasing shard count if consumer is already at max parallelism.
Symptom · 03
Firehose delivery failures to S3
Fix
1. Check CloudWatch DeliveryToS3.Success and BackupToS3.Success. 2. Verify S3 bucket policy allows Firehose writes. 3. Check Firehose buffer settings (size/interval). 4. Ensure data format is valid (e.g., JSON lines).
★ Amazon Kinesis: Real-Time Data Streaming and Analytics Triage Cheat SheetFirst-response commands for when things go wrong — copy-paste ready.
Throttling errors `WriteProvisionedThroughputExceeded`
Immediate action
Check CloudWatch metric for shard-level throttling
Commands
aws cloudwatch get-metric-statistics --namespace AWS/Kinesis --metric-name WriteProvisionedThroughputExceeded --dimensions Name=StreamName,Value=my-stream --start-time $(date -u -d '-5 minutes' +%Y-%m-%dT%H:%M:%SZ) --end-time $(date -u +%Y-%m-%dT%H:%M:%SZ) --period 60 --statistics Sum
aws kinesis describe-stream --stream-name my-stream --query 'StreamDescription.Shards[*].{Id:ShardId,HashKeyRange:HashKeyRange}'
Fix now
Increase shard count: aws kinesis update-shard-count --stream-name my-stream --target-shard-count 20 --scaling-type UNIFORM_SCALING
High iterator age `MillisBehindLatest` > 5000+
Immediate action
Check consumer processing time
Commands
aws cloudwatch get-metric-statistics --namespace AWS/Kinesis --metric-name MillisBehindLatest --dimensions Name=StreamName,Value=my-stream --start-time $(date -u -d '-5 minutes' +%Y-%m-%dT%H:%M:%SZ) --end-time $(date -u +%Y-%m-%dT%H:%M:%SZ) --period 60 --statistics Maximum
aws lambda get-function-configuration --function-name my-consumer --query 'Environment.Variables'
Fix now
Increase Lambda batch size to 10000 and concurrency to 100: aws lambda update-function-configuration --function-name my-consumer --environment Variables={BATCH_SIZE=10000} --reserved-concurrent-executions 100
Firehose delivery failures+
Immediate action
Check Firehose delivery status
Commands
aws firehose describe-delivery-stream --delivery-stream-name my-firehose --query 'DeliveryStreamDescription.Destinations[0].DestinationId'
aws cloudwatch get-metric-statistics --namespace AWS/Firehose --metric-name DeliveryToS3.Success --dimensions Name=DeliveryStreamName,Value=my-firehose --start-time $(date -u -d '-5 minutes' +%Y-%m-%dT%H:%M:%SZ) --end-time $(date -u +%Y-%m-%dT%H:%M:%SZ) --period 60 --statistics Sum
Fix now
Update buffer interval to 60s: aws firehose update-destination --delivery-stream-name my-firehose --current-delivery-stream-version-id 1 --destination-id destination-1 --s3-destination-update '{"BufferingHints":{"IntervalInSeconds":60}}'
Data Analytics Flink job OOM+
Immediate action
Check Flink job manager logs
Commands
aws kinesisanalyticsv2 describe-application --application-name my-flink-app --query 'ApplicationDetail.ApplicationConfigurationDescription.EnvironmentPropertyDescriptions[0].PropertyGroups'
aws cloudwatch get-metric-statistics --namespace AWS/KinesisAnalytics --metric-name KPUCount --dimensions Name=Application,Value=my-flink-app --start-time $(date -u -d '-5 minutes' +%Y-%m-%dT%H:%M:%SZ) --end-time $(date -u +%Y-%m-%dT%H:%M:%SZ) --period 60 --statistics Average
Fix now
Increase task manager memory: aws kinesisanalyticsv2 update-application --application-name my-flink-app --application-configuration-update '{"EnvironmentPropertyUpdates":{"PropertyGroups":[{"PropertyGroupId":"FlinkApplicationProperties","PropertyMap":{"taskmanager.memory.process.size":"4g"}}]}}'
Feature / AspectKinesis Data StreamsKinesis Data Firehose
Latency< 1 second60+ seconds (buffered)
Consumer controlFull (KCL, Lambda, custom)None (managed delivery)
Replay capabilityUp to 7 daysNone (delivered once)
Cost modelPer shard-hourPer GB ingested
Best forReal-time processing, multi-consumerSimple delivery to S3/Redshift
⚙ Quick Reference
6 commands from this guide
FileCommand / CodePurpose
shard_calculator.pydef calculate_shards(peak_mbps: float, avg_record_size_kb: float, records_per_se...Shard Calculus
kinesis_producer.pyfrom botocore.config import ConfigProducer Patterns
lambda_consumer.pydef lambda_handler(event, context):Consumer Design
dlq_handler.pysqs = boto3.client('sqs')Error Handling and Retries
cloudwatch_alarm.json{Monitoring and Alerting
auto_scaling_policy.json{Cost Optimization

Key takeaways

1
Size shards for peak throughput, not average. Use high-cardinality partition keys to avoid hot shards.
2
Always implement a dead-letter queue for Lambda consumers to prevent poison pills from causing infinite retries.
3
Auto-scale shards with target tracking on IncomingBytes to avoid over-provisioning costs.
4
Kinesis is not a message queue. Use SQS for individual message delivery and SNS for fan-out.
INTERVIEW PREP · PRACTICE MODE

Interview Questions on This Topic

Q01SENIOR
How does Kinesis handle shard splitting and merging under the hood? What...
Q02SENIOR
When would you choose Kinesis Data Streams over Apache Kafka in a produc...
Q03SENIOR
What happens when a Lambda consumer processing a Kinesis stream throws a...
Q04JUNIOR
What is the maximum throughput of a single Kinesis shard?
Q05SENIOR
You see `ProvisionedThroughputExceededException` on a Kinesis stream but...
Q06SENIOR
Design a real-time analytics pipeline that ingests 50 MB/s of clickstrea...
Q01 of 06SENIOR

How does Kinesis handle shard splitting and merging under the hood? What happens to data during a reshard?

ANSWER
Kinesis splits a parent shard into two child shards. The parent shard's hash key range is divided between the children. Data in the parent remains readable for the retention period. New data goes to children. Consumers must discover new shards via the KCL or API. Merging combines two adjacent shards into one. The parent shards become inactive after the merge.
FAQ · 4 QUESTIONS

Frequently Asked Questions

01
What's the difference between Kinesis Data Streams and Kinesis Data Firehose?
02
How do I handle duplicate records in Kinesis?
03
How do I scale Kinesis shards automatically?
04
What happens if a Kinesis shard fails?
N
Naren Founder & Principal Engineer

20+ years shipping production infrastructure and CI/CD at scale. Drawn from code that ran under real load.

Follow
Verified
production tested
July 18, 2026
last updated
2,466
articles · all by Naren
🔥

That's AWS. Mark it forged?

3 min read · try the examples if you haven't

Previous
AWS CDK: Infrastructure as Code with Programming Languages
44 / 63 · AWS
Next
AWS SAM: Serverless Application Model