Kinesis Shard Separation Using AWS IoT Topic Payloads

Kinesis Shard Separation Using AWS IoT Topic Payloads

Routing IoT topic payloads to specific Kinesis shards by customer ID, keeping each customer's records in order downstream.

Takahiro Iwasa
3 min read

Kinesis shards can be separated by IoT topic payload, which is useful when records for different customers need to land on different shards for downstream processing. The example below routes records by customer_id.

Prerequisites

The following need to be installed on your machine:

Building

The AWS IoT topic rule specifies PartitionKey: ${customer_id} (line 37), which determines how records are distributed across shards.

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Resources:
Lambda:
Type: AWS::Serverless::Function
Properties:
CodeUri: ./src/
Events:
Kinesis:
Type: Kinesis
Properties:
BatchSize: 100
BisectBatchOnFunctionError: true
Enabled: true
StartingPosition: LATEST
Stream: !GetAtt Kinesis.Arn
FunctionName: aws_iot_kinesis_partition_lambda
Handler: lambda_function.lambda_handler
Role: !GetAtt IamRole.Arn
Runtime: python3.8
Kinesis:
Type: AWS::Kinesis::Stream
Properties:
Name: aws_iot_kinesis_partition_stream
RetentionPeriodHours: 24
ShardCount: 1
TopicRule:
Type: AWS::IoT::TopicRule
Properties:
RuleName: aws_iot_kinesis_partition_topic_rule
TopicRulePayload:
Actions:
- Kinesis:
PartitionKey: ${customer_id}
RoleArn: !GetAtt IamRole.Arn
StreamName: aws_iot_kinesis_partition_stream
AwsIotSqlVersion: 2016-03-23
RuleDisabled: false
Sql: SELECT * FROM 'aws-iot-kinesis-partition-topic'
IamRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service: lambda.amazonaws.com
Action: sts:AssumeRole
- Effect: Allow
Principal:
Service: iot.amazonaws.com
Action: sts:AssumeRole
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
Policies:
- PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- kinesis:GetShardIterator
- kinesis:GetRecords
- kinesis:DescribeStream
- kinesis:PutRecord
Resource:
- !GetAtt Kinesis.Arn
PolicyName: policy
LogGroup:
Type: AWS::Logs::LogGroup
Properties:
LogGroupName: /aws/lambda/aws_iot_kinesis_partition_lambda
RetentionInDays: 1

The Lambda function that processes the streamed records is a short Python script:

lambda_function.py
import json
def lambda_handler(event, context):
for record in event['Records']:
print(json.dumps(record['kinesis']))

Build and deploy the stack with SAM:

Terminal window
sam build
sam deploy \
--stack-name aws-iot-kinesis-partition-lambda \
--capabilities CAPABILITY_NAMED_IAM

Testing

Test payloads can be sent to the AWS IoT topic directly:

Terminal window
aws iot-data publish \
--topic aws-iot-kinesis-partition-topic \
--payload '{"customer_id": 1, "message": "Hello from AWS IoT"}' \
--cli-binary-format raw-in-base64-out

Check the Lambda logs to confirm that the partitionKey matches the customer_id in your payload.

Cleaning Up

Remove the resources provisioned by this example with:

Terminal window
sam delete --stack-name aws-iot-kinesis-partition-lambda

Conclusion

Setting PartitionKey: ${customer_id} in the IoT topic rule routed records for the same customer to the same Kinesis shard, without any partitioning logic in the Lambda consumer itself. The routing happens entirely in that one expression — records are guaranteed to land on the same shard by the time they reach event['Records'], and that guarantee is what preserves per-customer ordering downstream, which matters for anything that processes a customer’s events sequentially. It’s worth remembering that this only spreads load evenly if customer_id values are reasonably uniform; a single outsized customer can still concentrate traffic on one shard even with the partition key set correctly.

About the author

Takahiro Iwasa

Takahiro Iwasa

Software Developer

This blog shares technical notes from hands-on projects—architecture, implementation, and AWS service integrations.