Apache Kafka is designed to process messages across multiple consumers using topic partitions. When integrating Kafka with AWS Lambda, however, developers sometimes assume that increasing the number of Lambda consumers will automatically increase message-processing throughput.
This assumption can lead to unexpected performance limitations.
AWS Lambda uses event source mappings to consume messages from Amazon Managed Streaming for Apache Kafka (Amazon MSK) and self-managed Kafka clusters. It manages the polling process and invokes Lambda functions with batches of records. Although Lambda supports automatic scaling and provisioned event pollers, Kafka partitioning still plays an important role in determining how messages are distributed and processed.
Understanding the relationship between Kafka partitions, Lambda event pollers, and function concurrency is essential when designing a scalable event-driven application.
This article explains how Lambda scales Kafka consumers, what happens when consumer capacity exceeds the number of partitions, and how to improve processing throughput without compromising message ordering.
Understanding Kafka Partitions and Lambda Consumers
A Kafka topic is divided into partitions. Each partition stores an ordered sequence of messages, and Kafka distributes these partitions across consumers within a consumer group.
For example, consider an application that processes customer transactions through a Kafka topic containing four partitions.
A simplified distribution might look like this:
| Kafka partition | Assigned consumer |
| Partition 0 | Consumer 1 |
| Partition 1 | Consumer 2 |
| Partition 2 | Consumer 3 |
| Partition 3 | Consumer 4 |
Each consumer processes messages from its assigned partition.
If the consumer group contains eight consumers but the topic has only four partitions, Kafka cannot assign all eight consumers an independent partition. The additional consumers remain idle for that topic.
AWS Lambda handles Kafka consumption differently from applications where developers manage Kafka consumer instances themselves. Lambda creates an event source mapping that polls Kafka and invokes the configured function.
The main components are:
- Kafka partitions: Divide messages into independently ordered sequences.
- Event source mapping: Connects the Kafka topic to a Lambda function.
- Event pollers: Retrieve messages from Kafka.
- Lambda execution environments: Execute the application code.
- Consumer group: Maintains the Kafka consumption position and coordination.
These components work together, but increasing one resource does not automatically increase the capacity of every other resource.
How AWS Lambda Scales Kafka Event Processing
AWS Lambda supports two event poller scaling modes for Amazon MSK and self-managed Apache Kafka.
On-demand scaling
On-demand mode is the default configuration.
Lambda automatically manages event pollers according to the workload. It evaluates partition offset lag and adjusts polling capacity when message-processing demand changes.
When messages arrive faster than the function can process them, partition lag increases. Lambda can respond by increasing the number of event pollers.
When the workload decreases, Lambda can reduce polling capacity.
This approach is useful for applications with variable traffic because developers do not need to manage polling resources manually.
However, scaling is not instantaneous. Lambda evaluates offset lag at regular intervals, and its scaling response can take time.
Provisioned scaling
Provisioned mode provides more control over Kafka event source mapping capacity.
Developers can configure:
- MinimumPollers: The minimum number of dedicated event pollers.
- MaximumPollers: The maximum number of event pollers.
- PollerGroupName: An optional configuration for sharing provisioned poller capacity across multiple event source mappings.
For Amazon MSK and self-managed Kafka, one event poller can support up to 5 MB per second of throughput or up to five concurrent Lambda invocations, depending on the workload.
Provisioned mode supports a minimum of 1 and a maximum of 200 minimum pollers. The maximum poller setting can range from 1 to 2,000.
However, there is an important limitation: to maintain ordered processing within partitions, Lambda caps the maximum number of event pollers at the number of partitions in the Kafka topic.
This means configuring 100 maximum pollers does not guarantee that Lambda will use 100 pollers when the topic contains only 20 partitions.
What Happens When Lambda Consumers Exceed Kafka Partitions?
Consider a Kafka topic with 10 partitions and a Lambda event source mapping configured with a maximum of 50 pollers.
Developers might expect Lambda to use all 50 pollers to increase processing capacity.
However, Lambda limits the maximum pollers to the number of partitions to preserve ordered processing within each partition.
The effective poller limit in this example is therefore 10.
| Configuration | Value |
| Kafka partitions | 10 |
| Configured minimum pollers | 5 |
| Configured maximum pollers | 50 |
| Effective maximum pollers | 10 |
| Additional independent partition capacity | 0 |
This does not mean that the Lambda function can only execute 10 concurrent invocations in every situation.
An event poller can support multiple concurrent invocations, depending on throughput, payload size, and processing duration. Function-level concurrency also affects the actual processing capacity.
The important distinction is between the number of event pollers and the number of Lambda invocations.
Adding more pollers beyond the partition limit does not create additional independent partition-level processing capacity.
Configuring Provisioned Pollers for Amazon MSK
Let's configure an existing Lambda event source mapping using the AWS CLI.
First, identify the event source mapping UUID:
aws lambda list-event-source-mappings \
--function-name OrderProcessorFunction
The response contains the event source mapping UUID and its current configuration.
Next, enable provisioned polling:
aws lambda update-event-source-mapping \
--uuid a1b2c3d4-5678-90ab-cdef-EXAMPLE \
--provisioned-poller-config '{
"MinimumPollers": 5,
"MaximumPollers": 20
}'
In this example:
- MinimumPollers maintains a baseline of five pollers.
- MaximumPollers allows Lambda to scale up to 20 pollers, subject to the partition limit and other service constraints.
For a topic with 10 partitions, the effective maximum poller count cannot exceed 10.
You should choose these values based on actual traffic rather than configuring the highest available limits.
Verifying the configuration
After updating the event source mapping, retrieve its configuration:
aws lambda get-event-source-mapping \
--uuid a1b2c3d4-5678-90ab-cdef-EXAMPLE
Check the ProvisionedPollerConfig property to verify the configured values.
You can also monitor the ProvisionedPollers CloudWatch metric to understand how many provisioned pollers are being used.
Improving Throughput Without Increasing Partition Count
Increasing Kafka partitions is not always the first solution to a throughput problem.
Before changing the topic architecture, evaluate the following options.
Optimize Lambda batch processing
Lambda receives Kafka messages in batches. Processing multiple records within a single invocation can reduce repeated initialization and invocation overhead.
For example, a Python Lambda function can process records from the Kafka event payload:
import json
def lambda_handler(event, context):
records = event.get("records", {})
for partition_records in records.values():
for record in partition_records:
message = json.loads(
record["value"]
)
process_order(message)
return {
"statusCode": 200,
"message": "Batch processed"
}
def process_order(order):
print("Processing order:", order)
This example assumes that the Kafka message value contains valid JSON.
In production, validate message formats, handle individual record failures, and make processing idempotent. A failure in a batch can result in records being delivered again.
Reduce Lambda execution time
Long-running function executions can reduce the rate at which messages are processed.
Review database calls, external API requests, serialization, and unnecessary network operations.
Where appropriate, use connection reuse, efficient database queries, and asynchronous operations.
However, asynchronous processing must preserve the ordering guarantees required by the application.
Increase Kafka partitions when necessary
If the application needs more independent partition-level processing capacity, increasing the number of partitions may be appropriate.
For example, moving from 10 to 20 partitions can provide more partition-level distribution opportunities.
However, partition expansion requires careful planning.
The partitioning key determines which partition receives a message. Changing the partition count can change the partition assignment for future messages with the same key.
Therefore, applications that depend on strict ordering for customer accounts or financial transactions should evaluate the impact before expanding a topic.
Understanding Message Ordering and Delivery Guarantees
Kafka guarantees message ordering within an individual partition, not across an entire topic.
AWS Lambda maintains ordered processing within partitions when consuming Kafka records. This is one reason the event poller limit is tied to the number of partitions.
Consider an order-processing application.
A customer might generate three events:
- OrderCreated
- PaymentConfirmed
- OrderShipped
If these events belong to the same customer or order, they should normally be published using a consistent partition key.
For example:
from kafka import KafkaProducer
import json
producer = KafkaProducer(
bootstrap_servers=["localhost:9092"],
value_serializer=lambda value:
json.dumps(value).encode("utf-8")
)
order_event = {
"orderId": "ORD-1001",
"customerId": "CUST-501",
"eventType": "OrderCreated"
}
producer.send(
"order-events",
key=b"CUST-501",
value=order_event
)
producer.flush()
Using the same key helps Kafka route related messages to the same partition, preserving their partition-level order.
However, ordering alone does not prevent duplicate processing.
Kafka event source mappings provide at-least-once processing semantics. A message can be delivered again when an invocation fails or when offset commitment does not complete as expected.
The Lambda function should therefore be idempotent.
For example, an order-processing service can maintain a processed-event identifier in a database and avoid applying the same business operation twice.
Comparison of Kafka Scaling Approaches
| Scaling approach | Main benefit | Important consideration |
| On-demand pollers | Automatic capacity adjustment | Scaling response takes time |
| Provisioned pollers | Configurable polling capacity | Additional cost |
| Lambda concurrency | Controls function execution capacity | Does not remove partition limits |
| Batch optimization | Improves processing efficiency | Requires suitable batch handling |
| Increasing partitions | More independent processing capacity | Changes partition distribution |
| Faster function execution | Improves processing rate | Requires application optimization |
The right approach depends on whether the bottleneck is Kafka consumption, Lambda execution, external dependencies, or the partition layout.
Monitoring and Troubleshooting Lambda Kafka Consumers
A scalable implementation requires monitoring both the Kafka topic and Lambda function.
Useful CloudWatch metrics include:
- ProvisionedPollers: Shows provisioned event poller usage.
- OffsetLag: Helps identify how far consumption is behind the latest Kafka messages.
- Errors: Indicates failed Lambda invocations.
- Throttles: Identifies function concurrency limitations.
- Duration: Shows how long Lambda invocations take.
- ConcurrentExecutions: Helps understand function execution capacity.
Common problems include:
| Problem | Possible cause | Recommended action |
| Increasing offset lag | Processing cannot keep up with incoming messages | Review batch size, execution duration, and poller capacity |
| Low poller utilization | Insufficient traffic or partition constraints | Review partition count and workload |
| Lambda throttling | Concurrency limit reached | Review reserved and account-level concurrency |
| Duplicate messages | At-least-once delivery | Implement idempotent processing |
| High processing latency | Slow downstream services | Optimize database and API operations |
| Uneven partition lag | Uneven message distribution | Review partition keys and producer behavior |
Avoid increasing poller capacity without checking downstream systems. More concurrent processing can increase database connections, API requests, and resource contention.
Best Practices for Production Applications
When designing a production Kafka-to-Lambda integration, consider the following practices:
- Choose partition keys carefully. Use business identifiers when related messages must maintain ordering.
- Monitor partition-level lag. Aggregate metrics can hide a single overloaded partition.
- Start with default scaling settings. Establish a performance baseline before enabling provisioned mode.
- Configure poller limits according to actual traffic, payload size, and processing duration.
- Set Lambda concurrency limits appropriately. Ensure that the function has sufficient capacity without overwhelming downstream services.
- Implement idempotency. Duplicate delivery should not create duplicate payments, orders, or database operations.
- Configure failure handling. Use retry limits, record age settings, and Kafka on-failure destinations where appropriate.
- Test partition expansion carefully. Verify key distribution and ordering requirements before changing an existing topic.
- Review operational costs. Provisioned pollers introduce additional charges, so monitor utilization.
- Test under realistic workloads. Include traffic spikes, slow downstream services, and failed message scenarios.
Advantages and Disadvantages
Advantages
- Provides more control over Kafka event source mapping throughput.
- Helps handle sudden traffic increases.
- Supports configurable minimum and maximum polling capacity.
- Reduces the need to manage Kafka consumer infrastructure manually.
- Provides visibility into provisioned poller usage through CloudWatch.
Disadvantages
- Introduces additional polling resource costs.
- Does not eliminate Kafka partition-level scaling constraints.
- Requires careful coordination with Lambda concurrency settings.
- Higher polling capacity can increase pressure on downstream services.
- Incorrect configuration can result in unnecessary infrastructure spending.
Summary
Scaling AWS Lambda consumers beyond Kafka topic partitions requires understanding the difference between polling capacity, function concurrency, and partition-level parallelism.
Provisioned event pollers provide greater control over Kafka consumption and can improve throughput for workloads with strict performance requirements. However, Lambda limits the maximum number of event pollers according to the number of Kafka partitions to maintain ordered processing within partitions.
Developers should not treat poller count and Lambda concurrency as interchangeable measurements. A poller can support multiple concurrent invocations, while the partition structure still determines independent processing opportunities.
For production applications, the best approach is to measure partition lag, optimize batch processing, reduce function execution time, configure appropriate polling capacity, and increase partitions only when additional partition-level parallelism is genuinely required.
A well-designed Kafka and Lambda integration balances throughput, ordering, reliability, and operating cost rather than simply increasing the number of consumers.
Join the conversation! Your thoughts help the community grow.