πŸŽ‰ 75% of content is free forever β€” Unlock Premium from $10/mo β†’
CW
πŸ’Ό Servicesℹ️ Aboutβœ‰οΈ ContactView Pricing Plansfrom $10

Amazon Kinesis for Data Engineers

AWS Data EngineeringKinesis Data Streams, Firehose & Analytics⭐ Premium

Advertisement

Amazon Kinesis for Data Engineers

Real-time data streaming at scale with Data Streams, Firehose, and Analytics for sub-second processing.

20 min readIntermediate

Why This Matters

Amazon Kinesis is the foundation of real-time data engineering on AWS. While batch processing handles historical data, Kinesis enables sub-second processing of live data streams from applications, IoT devices, logs, and clickstreams. Understanding shard capacity, partition key design, and consumer patterns is essential for building reliable streaming pipelines. Kinesis Data Firehose provides serverless delivery to S3, Redshift, and OpenSearch without managing infrastructure.

Key Insight: Kinesis Data Streams and Kinesis Data Firehose serve different purposes. KDS is for custom processing with shards and consumers. KDF is for serverless delivery to AWS services. Choosing the wrong one can lead to unnecessary complexity or cost.


Kinesis Architecture

Amazon Kinesis Ecosystem

ProducersWeb ApplicationsMobile AppsIoT SensorsCloudWatch LogsCustom ApplicationsKinesis AgentKinesis Data StreamsShard 1: 1 MB/s writeShard 2: 1 MB/s writeShard N: 1 MB/s writeRetention: 24h - 365 daysEnhanced Fan-OutServer-Side EncryptionKinesis Data FirehoseAuto-Scaling DeliveryLambda TransformationsFormat ConversionBuffering & BatchingCompression & EncryptionError Logging to S3Kinesis Data AnalyticsSQL on Streaming DataApache Flink ApplicationsWindow FunctionsDestinationsS3 Data LakeRedshiftOpenSearchHTTP EndpointsDatadog / SplunkKinesis ConsumersLambda FunctionsKCL (Java/Python)EMR / Glue StreamingMonitoringCloudWatch MetricsIteratorAgeGetRecords.IteratorAge

Kinesis Data Streams (KDS)

KDS is the foundation of the Kinesis platform, providing a durable, scalable stream for custom processing.

Shard Capacity

MetricPer ShardCalculation Example
Writeεžει‡1 MB/sec, 1,000 records/sec10 shards = 10 MB/s write
Readεžει‡2 MB/sec, 5 GetRecords/sec10 shards = 20 MB/s read
Enhanced Fan-Out2 MB/sec per consumer5 consumers = 10 MB/s per consumer
Retention24h default, 365 days maxExtended retention at additional cost

Shard Calculation Formula

Architecture Diagram
Write_Shard_Needed = ceil(Incoming_Data_Rate_MB_per_sec / 1)
Record_Shard_Needed = ceil(Incoming_Records_per_sec / 1000)
Shards_Required = max(Write_Shard_Needed, Record_Shard_Needed)

Example:
  5 MB/sec incoming, 4,000 records/sec
  Write shards: ceil(5 / 1) = 5
  Record shards: ceil(4000 / 1000) = 4
  Required shards: max(5, 4) = 5 shards

Production Code: Kinesis Operations

Create Kinesis Stream with Boto3

import boto3
import json
import logging
import time
from datetime import datetime

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)


def create_kinesis_stream(
    stream_name: str,
    shard_count: int = 4,
    retention_hours: int = 24,
    enable_enhanced_fan_out: bool = False
) -> dict:
    """
    Create a Kinesis Data Stream with production-grade settings.

    Args:
        stream_name: Unique stream identifier
        shard_count: Number of shards for throughput
        retention_hours: Data retention period (24-8760 hours)
        enable_enhanced_fan_out: Enable enhanced fan-out for consumers

    Returns:
        dict with stream details
    """
    kinesis_client = boto3.client('kinesis')

    stream_config = {
        'StreamName': stream_name,
        'ShardCount': shard_count,
        'StreamModeDetails': {
            'StreamMode': 'PROVISIONED'
        },
        'Tags': {
            'Environment': 'production',
            'ManagedBy': 'boto3'
        }
    }

    try:
        response = kinesis_client.create_stream(**stream_config)
        logger.info(f"Created stream: {stream_name} with {shard_count} shards")

        # Wait for stream to become active
        waiter = kinesis_client.get_waiter('stream_exists')
        logger.info("Waiting for stream to become active...")
        waiter.wait(StreamName=stream_name)

        # Update retention period if needed
        if retention_hours > 24:
            kinesis_client.increase_stream_retention_period(
                StreamName=stream_name,
                RetentionPeriodHours=retention_hours
            )
            logger.info(f"Increased retention to {retention_hours} hours")

        # Enable enhanced fan-out if requested
        if enable_enhanced_fan_out:
            register_enhanced_consumer(stream_name, f"{stream_name}-consumer")

        return {
            'stream_name': stream_name,
            'shard_count': shard_count,
            'retention_hours': retention_hours,
            'status': 'ACTIVE'
        }

    except kinesis_client.exceptions.ResourceInUseException:
        logger.warning(f"Stream {stream_name} already exists")
        return describe_stream(stream_name)
    except Exception as e:
        logger.error(f"Failed to create stream: {str(e)}")
        raise


def put_records_to_stream(
    stream_name: str,
    records: list,
    partition_key_extractor: callable = None
) -> dict:
    """
    Put records to a Kinesis stream with error handling.

    Args:
        stream_name: Target stream name
        records: List of record dictionaries with 'data' and optional 'partition_key'
        partition_key_extractor: Function to extract partition key from record

    Returns:
        dict with success count and failed records
    """
    kinesis_client = boto3.client('kinesis')

    formatted_records = []
    for i, record in enumerate(records):
        partition_key = record.get('partition_key')
        if not partition_key and partition_key_extractor:
            partition_key = partition_key_extractor(record)
        elif not partition_key:
            partition_key = f"pk-{i}"

        formatted_records.append({
            'Data': json.dumps(record['data']).encode('utf-8'),
            'PartitionKey': partition_key
        })

    failed_records = []
    success_count = 0

    # Process in batches of 500 (Kinesis limit)
    for i in range(0, len(formatted_records), 500):
        batch = formatted_records[i:i + 500]

        try:
            response = kinesis_client.put_records(
                StreamName=stream_name,
                Records=batch
            )

            success_count += response['Records'].__len__() - response['FailedRecordCount']
            failed_records.extend([
                {'index': i + idx, 'error_code': r.get('ErrorCode'), 'error_msg': r.get('ErrorMessage')}
                for idx, r in enumerate(response['Records'])
                if 'ErrorCode' in r
            ])

            if response['FailedRecordCount'] > 0:
                logger.warning(f"Batch {i // 500}: {response['FailedRecordCount']} records failed")

        except Exception as e:
            logger.error(f"Batch {i // 500} failed completely: {str(e)}")
            failed_records.extend([{'index': i + j, 'error': str(e)} for j in range(len(batch))])

    result = {
        'total_records': len(records),
        'success_count': success_count,
        'failed_count': len(failed_records),
        'failed_records': failed_records
    }

    logger.info(f"Put {success_count}/{len(records)} records to {stream_name}")
    return result


def register_enhanced_consumer(stream_name: str, consumer_name: str) -> str:
    """Register an enhanced fan-out consumer."""
    kinesis_client = boto3.client('kinesis')

    response = kinesis_client.register_stream_consumer(
        StreamName=stream_name,
        ConsumerName=consumer_name
    )

    consumer_arn = response['Consumer']['ConsumerARN']
    logger.info(f"Registered enhanced consumer: {consumer_arn}")
    return consumer_arn


def describe_stream(stream_name: str) -> dict:
    """Get stream details including shard information."""
    kinesis_client = boto3.client('kinesis')

    response = kinesis_client.describe_stream(StreamName=stream_name)
    stream_desc = response['StreamDescription']

    return {
        'stream_name': stream_desc['StreamName'],
        'stream_arn': stream_desc['StreamARN'],
        'shard_count': len(stream_desc['Shards']),
        'retention_hours': stream_desc['RetentionPeriodHours'],
        'status': stream_desc['StreamStatus'],
        'creation_time': str(stream_desc['StreamCreationTimestamp'])
    }


def get_shard_iterator(stream_name: str, shard_id: str, iterator_type: str = 'LATEST') -> str:
    """Get a shard iterator for reading records."""
    kinesis_client = boto3.client('kinesis')

    response = kinesis_client.get_shard_iterator(
        StreamName=stream_name,
        ShardId=shard_id,
        ShardIteratorType=iterator_type
    )

    return response['ShardIterator']

Kinesis Data Firehose

Firehose is a fully managed service for delivering streaming data to AWS data stores.

Delivery Destinations

DestinationFormat SupportBuffer Settings
S3Parquet, ORC, Avro, JSON, CSV1-128 MB, 60-900 sec
RedshiftCOPY via S3 staging1-128 MB, 60-900 sec
OpenSearchJSON1-100 MB, 60-900 sec
HTTP EndpointsJSON, Avro, Parquet1-128 MB, 60-900 sec

Firehose Configuration Example

import boto3
import json
import logging

logger = logging.getLogger(__name__)


def create_firehose_delivery_stream(
    stream_name: str,
    s3_destination: str,
    iam_role_arn: str,
    buffer_size_mb: int = 5,
    buffer_interval_sec: int = 300,
    compression: str = "GZIP",
    format_conversion: str = "PARQUET"
) -> dict:
    """
    Create a Kinesis Data Firehose delivery stream.

    Args:
        stream_name: Delivery stream name
        s3_destination: S3 bucket ARN for delivery
        iam_role_arn: IAM role ARN for Firehose
        buffer_size_mb: Buffer size in MB (1-128)
        buffer_interval_sec: Buffer interval in seconds (60-900)
        compression: Compression format (GZIP, SNAPPY, ZIP)
        format_conversion: Target format (PARQUET, ORC, AVRO)

    Returns:
        dict with delivery stream details
    """
    firehose_client = boto3.client('firehose')

    s3_config = {
        'RoleARN': iam_role_arn,
        'BucketARN': s3_destination,
        'Prefix': 'raw/year=!{timestamp:yyyy}/month=!{timestamp:MM}/day=!{timestamp:dd}/',
        'ErrorOutputPrefix': 'errors/',
        'BufferingHints': {
            'SizeInMBs': buffer_size_mb,
            'IntervalInSeconds': buffer_interval_sec
        },
        'CompressionFormat': compression,
        'CloudWatchLoggingOptions': {
            'Enabled': True,
            'LogGroupName': '/aws/kinesis-firehose',
            'LogStreamName': stream_name
        }
    }

    if format_conversion:
        s3_config['ProcessingConfiguration'] = {
            'Enabled': True,
            'Processors': [{
                'Type': 'ConvertRecord',
                'Parameters': [{
                    'ParameterName': 'OutputFormat',
                    'ParameterValue': format_conversion
                }]
            }]
        }

    try:
        response = firehose_client.create_delivery_stream(
            DeliveryStreamName=stream_name,
            DeliveryStreamType='DirectPut',
            S3DestinationConfiguration=s3_config,
            Tags={
                'Environment': 'production',
                'ManagedBy': 'boto3'
            }
        )

        stream_arn = response['DeliveryStreamARN']
        logger.info(f"Created Firehose stream: {stream_arn}")
        return {'stream_arn': stream_arn, 'status': 'CREATING'}

    except firehose_client.exceptions.ResourceInUseException:
        logger.warning(f"Firehose stream {stream_name} already exists")
        return describe_firehose_stream(stream_name)
    except Exception as e:
        logger.error(f"Failed to create Firehose stream: {str(e)}")
        raise


def describe_firehose_stream(stream_name: str) -> dict:
    """Get Firehose delivery stream details."""
    firehose_client = boto3.client('firehose')

    response = firehose_client.describe_delivery_stream(
        DeliveryStreamName=stream_name
    )

    stream_desc = response['DeliveryStreamDescription']
    return {
        'stream_name': stream_desc['DeliveryStreamName'],
        'stream_arn': stream_desc['DeliveryStreamARN'],
        'status': stream_desc['DeliveryStreamStatus'],
        'creation_time': str(stream_desc['CreateTimestamp']),
        'destinations': len(stream_desc.get('Destinations', []))
    }

Real-World Project Structure

Architecture Diagram
kinesis-streaming-project/
β”œβ”€β”€ producers/
β”‚   β”œβ”€β”€ web_app_producer.py
β”‚   β”œβ”€β”€ iot_producer.py
β”‚   β”œβ”€β”€ log_agent_config.json
β”‚   └── kinesis_agent_config.json
β”œβ”€β”€ streams/
β”‚   β”œβ”€β”€ data_streams/
β”‚   β”‚   β”œβ”€β”€ create_streams.py
β”‚   β”‚   └── reshard_stream.py
β”‚   └── firehose/
β”‚       β”œβ”€β”€ create_delivery_streams.py
β”‚       └── firehose_config.json
β”œβ”€β”€ consumers/
β”‚   β”œβ”€β”€ lambda_consumer/
β”‚   β”‚   β”œβ”€β”€ handler.py
β”‚   β”‚   └── requirements.txt
β”‚   β”œβ”€β”€ kcl_consumer/
β”‚   β”‚   β”œβ”€β”€ consumer.py
β”‚   β”‚   └── config.properties
β”‚   └── flink_consumer/
β”‚       β”œβ”€β”€ streaming_job.py
β”‚       └── pom.xml
β”œβ”€β”€ analytics/
β”‚   β”œβ”€β”€ kda_sql_app.sql
β”‚   β”œβ”€β”€ kda_flink_app.py
β”‚   └── window_functions.sql
β”œβ”€β”€ infrastructure/
β”‚   β”œβ”€β”€ cloudformation/
β”‚   β”‚   β”œβ”€β”€ kinesis_streams.yaml
β”‚   β”‚   β”œβ”€β”€ kinesis_firehose.yaml
β”‚   β”‚   └── iam_roles.yaml
β”‚   └── terraform/
β”‚       β”œβ”€β”€ main.tf
β”‚       β”œβ”€β”€ streams.tf
β”‚       └── variables.tf
β”œβ”€β”€ monitoring/
β”‚   β”œβ”€β”€ cloudwatch_dashboard.json
β”‚   β”œβ”€β”€ alarms.yaml
β”‚   └── metrics_collector.py
└── docs/
    β”œβ”€β”€ architecture.md
    β”œβ”€β”€ capacity_planning.md
    └── runbook.md

Mathematical Formations

Kinesis Cost Estimation

Architecture Diagram
Data Streams Cost:
  Shard_Cost = Shard_Count * $0.015/hr * Hours
  PUT_Cost = (Total_PUT_Units / 25KB) * $0.00000025
  Enhanced_Fan-Out = Consumer_Count * $0.015/hr * Hours

  Example:
    10 shards * $0.015 * 24 * 30 = $108/month (shards only)
    1M PUTs/hour: (1,000,000 / 25,000) * $0.00000025 * 24 * 30 = $0.72/month

Firehose Cost:
  Data_Cost = Data_Ingested_GB * $0.029 (first 10 TB/month)
  Transform_Cost = Data_Transformed_GB * $0.006
  Format_Conversion = Data_Converted_GB * $0.025

  Example:
    500 GB/day ingested: 0.5 * 30 * $0.029 = $0.435/month

Throughput Calculation

Architecture Diagram
Max_Throughput = Shards * 1 MB/sec (write)
Max_Records = Shards * 1,000 records/sec

Required_Shards = ceil(Max(Data_Rate_MB, Records_Rate_KB / 1))

Example:
  10 MB/sec incoming, 8,000 records/sec
  Data shards: ceil(10) = 10
  Record shards: ceil(8000/1000) = 8
  Required: max(10, 8) = 10 shards

Performance Considerations

FactorImpactOptimization Strategy
Shard CountDirectly limits throughputRight-size based on data rate, use auto-scaling
Partition KeyDetermines shard assignmentChoose high-cardinality keys for even distribution
Batch SizeAffects PUT latencyUse PutRecords with batches of 500
Enhanced Fan-OutDedicated throughput per consumerUse when multiple consumers read same stream
Buffer SizeDelivery latency vs efficiencyLarger buffers reduce cost but increase latency
CompressionReduces data transfer costsUse GZIP for JSON, Snappy for Avro
Lambda TransformationsAdds latency but enables enrichmentKeep Lambda functions efficient (< 1 second)

Security Considerations

Security LayerImplementationPriority
Encryption at RestKMS server-side encryptionCritical
Encryption in TransitTLS 1.2 for all API callsCritical
IAM PoliciesLeast-privilege for producers and consumersCritical
VPC EndpointsPrivate connectivity without internetHigh
Resource PoliciesCross-account access controlHigh
CloudTrailLog all Kinesis API callsHigh
Stream ARN RestrictionsLimit access to specific streamsMedium
Tag-Based AccessControl access using resource tagsMedium

Interview Questions & Answers

Q1: What is the difference between Kinesis Data Streams and Kinesis Data Firehose?

Answer: KDS is for custom processing - you manage shards, consumers, and processing logic. It provides 2MB/sec read throughput per shard with enhanced fan-out options. KDF is for serverless delivery - it automatically scales and delivers to S3, Redshift, or OpenSearch without managing infrastructure.

Use KDS when:

  • You need custom processing logic
  • Multiple consumers read from the same stream
  • You need sub-second latency
  • You want control over data ordering per shard

Use KDF when:

  • You want serverless delivery to AWS services
  • You can tolerate buffer delays (60-900 seconds)
  • You want format conversion (JSON to Parquet)
  • You prefer zero infrastructure management

Q2: How do you determine the number of shards needed?

Answer: Shard calculation formula:

  1. Write throughput: Divide expected data rate by 1 MB/sec per shard
  2. Record throughput: Divide expected records/sec by 1,000 records/sec per shard
  3. Required shards: Take the maximum of the two calculations

Example:

  • Expected data rate: 5 MB/sec
  • Expected records: 4,000 records/sec
  • Write shards: ceil(5 / 1) = 5 shards
  • Record shards: ceil(4000 / 1000) = 4 shards
  • Required: max(5, 4) = 5 shards

Also consider:

  • Peak vs average load (use average for on-demand mode)
  • Enhanced fan-out adds 2MB/sec per consumer
  • Use CloudWatch IncomingBytes and IncomingRecords to monitor

Q3: What is a partition key and why is it important?

Answer: A partition key determines which shard receives each record. Records with the same partition key always go to the same shard, ensuring ordering within that shard.

Importance:

  1. Data ordering: Records with the same key are processed in order
  2. Load distribution: Uneven keys cause hot shards
  3. Consumer design: Related data should use the same key

Best practices:

  • Use high-cardinality keys (many unique values)
  • Avoid low-cardinality keys (e.g., boolean, status codes)
  • Use UUID or customer ID for even distribution
  • Monitor WriteProvisionedThroughputExceeded for hot shards

Q4: How does Kinesis handle data ordering?

Answer: Kinesis guarantees ordering at the shard level. Records with the same partition key are placed in the same shard and are delivered in order to consumers.

Key points:

  • Ordering is per-shard, not across shards
  • Partition key determines shard assignment
  • Enhanced fan-out maintains ordering per consumer
  • KCL (Kinesis Client Library) handles shard splitting and lease management

Limitations:

  • No ordering across shards
  • Shard splitting can temporarily break ordering
  • Replaying records may affect ordering guarantees

Q5: What is the Kinesis Client Library (KCL)?

Answer: KCL is a library that simplifies building custom consumers for Kinesis streams. It handles:

  1. Shard discovery: Automatically discovers and tracks shards
  2. Lease management: Coordinates multiple consumers across shards
  3. Checkpoint tracking: Tracks processing progress for fault tolerance
  4. Load balancing: Distributes shards across consumer instances
  5. Heartbeat monitoring: Detects and replaces failed consumers

KCL implementations exist for Java, Python (amazon-kinesis-client), and other languages. It's essential for building production-grade consumers that need exactly-once processing semantics.

Q6: How do you handle duplicate records in Kinesis?

Answer: Kinesis provides at-least-once delivery by default. Duplicates can occur during network retries, shard splits, or consumer failures.

Strategies for handling duplicates:

  1. Idempotent processing: Design consumers to handle duplicates gracefully
  2. Deduplication tables: Use DynamoDB to track processed record IDs
  3. Sequence numbers: Use Kinesis sequence numbers for ordering
  4. Timestamps: Include event timestamps for time-based deduplication
  5. Consumer checkpointing: Use KCL checkpointing for consistent offsets

For exactly-once semantics, use KCL with DynamoDB-based lease management and implement idempotent writes.

Q7: What is enhanced fan-out and when would you use it?

Answer: Enhanced fan-out provides dedicated 2MB/sec read throughput per registered consumer, reducing contention compared to shared throughput.

Use enhanced fan-out when:

  • Multiple consumers read from the same stream
  • Each consumer needs consistent, dedicated throughput
  • You want to avoid read throttling
  • Low-latency processing is critical (sub-second)

Cost consideration:

  • Standard reads: Shared 2MB/sec per shard
  • Enhanced fan-out: $0.015 per consumer per hour per shard
  • Worth it when 3+ consumers need dedicated throughput

Q8: How does Kinesis Data Analytics differ from Apache Flink?

Answer:

AspectKDA SQLKDA FlinkSelf-Managed Flink
LanguageStandard SQLJava/ScalaJava/Scala
ComplexityLowMediumHigh
State ManagementManaged by AWSManaged by AWSSelf-managed
ScalingAuto-scalingAuto-scalingManual
CostPer KPU-hourPer KPU-hourEC2 costs

Use KDA SQL for:

  • Simple windowed aggregations
  • SQL-based streaming analytics
  • Quick prototyping

Use KDA Flink for:

  • Complex event processing
  • Custom operators and transformations
  • Stateful computations
  • Integration with external systems

Common Pitfalls

PitfallImpactPrevention
Insufficient shardsWrite throttling, data lossMonitor CloudWatch, use auto-scaling
Poor partition keyHot shards, uneven loadUse high-cardinality keys, monitor shard usage
Missing error handlingSilent failures, data lossImplement retry logic, use DLQ for Firehose
Ignoring iterator ageConsumer lag, data expirationMonitor IteratorAge metric, scale consumers
Over-provisioning shardsUnnecessary costUse on-demand mode for variable workloads
No encryptionSecurity vulnerabilitiesEnable KMS encryption for all streams
Missing monitoringIssues discovered lateSet up CloudWatch alarms for key metrics
Firehose buffer too smallHigher costs, more S3 objectsBalance buffer size with latency requirements

Why This Matters for Your Career

Kinesis expertise is essential for real-time data engineering roles. Understanding shard management, partition key design, and consumer patterns demonstrates your ability to build reliable streaming pipelines. Kinesis is the foundation for real-time analytics, IoT processing, and event-driven architectures. Mastering Kinesis concepts will enhance your candidacy for roles requiring streaming data expertise.


Key Takeaways

  • KDS provides custom processing with shards and consumers for sub-second latency
  • KDF provides serverless delivery to S3, Redshift, and OpenSearch
  • Shard count directly limits throughput - right-size based on data rate
  • Partition key design is critical for even distribution and ordering
  • Enhanced fan-out provides dedicated throughput for multiple consumers
  • At-least-once delivery requires idempotent consumer design


See Also

πŸ”’

Premium Content

Amazon Kinesis for Data Engineers

You've previewed the first section. Unlock this full lesson and 900+ advanced tutorials with a Premium plan.

🎯End-to-end Projects
πŸ’ΌInterview Prep
πŸ“œCertificates
🀝Community Access

Already a member? Log in

Advertisement