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..
20+ years shipping production infrastructure and CI/CD at scale. Drawn from code that ran under real load.
- ✓AWS account with Kinesis access
- ✓Basic understanding of stream processing (Kafka, Pub/Sub)
- ✓Familiarity with IAM roles and CloudWatch metrics
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.
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.
| Chrome | Firefox | Safari | Edge |
|---|---|---|---|
| ✓ | ✓ | ✓ | ✓ |
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.
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).
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).
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.
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.
{ "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.
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.
The 4GB Container That Kept Dying
OutOfMemoryError: Java heap space every 30 minutes.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.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.- Always profile key cardinality before using keyBy in Flink.
- High cardinality kills state backends.
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.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).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 Sumaws kinesis describe-stream --stream-name my-stream --query 'StreamDescription.Shards[*].{Id:ShardId,HashKeyRange:HashKeyRange}'aws kinesis update-shard-count --stream-name my-stream --target-shard-count 20 --scaling-type UNIFORM_SCALING| File | Command / Code | Purpose |
|---|---|---|
| shard_calculator.py | def calculate_shards(peak_mbps: float, avg_record_size_kb: float, records_per_se... | Shard Calculus |
| kinesis_producer.py | from botocore.config import Config | Producer Patterns |
| lambda_consumer.py | def lambda_handler(event, context): | Consumer Design |
| dlq_handler.py | sqs = boto3.client('sqs') | Error Handling and Retries |
| cloudwatch_alarm.json | { | Monitoring and Alerting |
| auto_scaling_policy.json | { | Cost Optimization |
Key takeaways
Interview Questions on This Topic
How does Kinesis handle shard splitting and merging under the hood? What happens to data during a reshard?
Frequently Asked Questions
20+ years shipping production infrastructure and CI/CD at scale. Drawn from code that ran under real load.
That's AWS. Mark it forged?
3 min read · try the examples if you haven't