AWS Builder Center
Designing Resilient SQS and Lambda Pipelines: Retries, Partial Failures, and Idempotency

Designing Resilient SQS and Lambda Pipelines: Retries, Partial Failures, and Idempotency

Designing Resilient SQS and Lambda Pipelines: Retries, Partial Failures, and Idempotency

Security engineer
Designing Resilient SQS and Lambda Pipelines: Retries, Partial Failures, and Idempotency
Introductions
Amazon SQS and AWS Lambda are widely used to build serverless applications that process events asynchronously. Although the integration is straightforward to configure, production environments introduce challenges such as duplicate messages, poison messages, repeated processing, and unexpected failures.
This article explores three essential practices for building reliable SQS-to-Lambda pipelines: partial batch responses, dead-letter queues, and idempotent processing. Together, these practices help prevent unnecessary retries, isolate unprocessable messages, and protect downstream systems from duplicate operations.
The Problem: At-Least-Once Delivery
Amazon SQS Standard queues provide at-least-once message delivery. This means a message may be delivered more than once, even when the consumer has already processed it successfully.
Lambda also processes SQS messages in batches. By default, when a batch fails, messages that were processed successfully may be returned to the queue alongside the failed message.
This creates two common problems.
Duplicate side effects occur when an operation, such as charging a customer or sending a notification, is performed more than once.
Retry amplification occurs when a single malformed message causes several healthy messages to be processed repeatedly, wasting compute resources and increasing latency.
Practice 1: Report Partial Batch Failures
Partial batch responses allow a Lambda function to identify individual messages that failed during processing. Instead of returning the entire batch to the queue, Lambda retries only the messages reported as failures.
First, enable ReportBatchItemFailures on the SQS event source mapping. In AWS SAM, the configuration looks like this:
Events: OrdersQueue: Type: SQS Properties: Queue: !GetAtt OrdersQueue.Arn BatchSize: 10 FunctionResponseTypes: - ReportBatchItemFailures
Next, update the Python handler to return the identifiers of failed messages.
def handler(event, context): failures = []
``` for record in event["Records"]: try: process(record) except Exception: failures.append({ "itemIdentifier": record["messageId"] })
return {"batchItemFailures": failures} ```
The process function represents your application's business logic and should be implemented separately.
With this configuration, successful messages can be removed from the queue while failed messages remain eligible for retry. In production, log exceptions with sufficient context to diagnose failures, and avoid catching errors that should terminate the entire invocation without understanding their implications.
AWS Lambda Powertools for Python also provides batch-processing utilities that simplify partial batch response handling.
Practice 2: Isolate Poison Messages with a Dead-Letter Queue
Some messages will never succeed without intervention. Examples include malformed payloads, invalid business data, and references to resources that no longer exist.
A dead-letter queue (DLQ) isolates messages that exceed a configured receive threshold. This prevents repeatedly failing messages from consuming resources indefinitely.
For example, an AWS CloudFormation queue configuration could look like this:
OrdersQueue: Type: AWS::SQS::Queue Properties: VisibilityTimeout: 180 RedrivePolicy: deadLetterTargetArn: !GetAtt OrdersDLQ.Arn maxReceiveCount: 5
The example assumes a Lambda function timeout of 30 seconds. AWS recommends setting the source queue's visibility timeout to at least six times the function timeout when using an SQS event source mapping.
Several operational considerations matter here.
Configure the redrive policy on the source SQS queue. The dead-letter queue configuration for asynchronous Lambda invocations serves a different purpose and does not replace the SQS redrive policy.
Monitor the DLQ using Amazon CloudWatch. An alarm on ApproximateNumberOfMessagesVisible can notify your team when messages accumulate without being resolved.
Establish a recovery process. Investigate the root cause, correct the underlying issue, and use the SQS dead-letter queue redrive feature when the messages are safe to process again.
Choose maxReceiveCount carefully. A low value may send temporarily failing messages to the DLQ too quickly, while an unnecessarily high value can waste resources.
Practice 3: Make Processing Idempotent
Because retries and duplicate deliveries are expected, message processing should be designed so that repeating the same logical operation does not produce duplicate side effects.
This property is known as idempotency.
A common approach is to store an idempotency key in Amazon DynamoDB and use conditional writes to prevent duplicate operations. AWS Lambda Powertools for Python provides an idempotency utility that supports persistent records and in-progress execution tracking.
For example:
from aws_lambda_powertools.utilities.idempotency import ( DynamoDBPersistenceLayer, idempotent_function, IdempotencyConfig )
persistence = DynamoDBPersistenceLayer( table_name="IdempotencyTable" )
config = IdempotencyConfig( event_key_jmespath="order_id" )
@idempotent_function( data_keyword_argument="order", persistence_store=persistence, config=config ) def charge_customer(order: dict): # Payment processing logic goes here return { "status": "charged", "order_id": order["order_id"] }
This example illustrates the pattern and requires the appropriate DynamoDB table, IAM permissions, and compatible Powertools configuration. The function must also be invoked using the expected order argument.
The choice of idempotency key is particularly important. A business identifier such as order_id is usually more useful than the SQS messageId because a producer retry can create a new message ID for the same logical order.
Set an appropriate expiry period for idempotency records. DynamoDB TTL can remove expired records, but deletion is asynchronous, so application logic should respect the configured expiry rather than relying on immediate physical deletion.
Consider concurrent execution as well. Two invocations may attempt to process the same business operation simultaneously. Conditional writes and in-progress state handling help coordinate these requests, but the external system must also be considered.
For payment operations, use the payment provider's idempotency mechanism whenever available. A database record alone cannot guarantee exactly-once external side effects if a request succeeds remotely but the Lambda function fails before recording the result.
Operational Checklist
Before deploying an SQS-to-Lambda pipeline to production, verify the following:
ReportBatchItemFailures is enabled and tested with both successful and deliberately failing messages.
The source queue has a redrive policy with an appropriate maximum receive count.
The visibility timeout is at least six times the Lambda function timeout, with additional consideration for batching and operational requirements.
CloudWatch alarms monitor DLQ depth and other important processing failures.
Handlers use idempotency for operations that could cause duplicate external side effects.
Structured logs include the business identifier, SQS message ID, and relevant error details.
Lambda concurrency is configured appropriately to protect downstream databases and APIs from sudden traffic increases.
Recovery procedures cover investigation, correction, and safe redrive of failed messages.
When to Consider Alternatives
SQS FIFO queues are worth evaluating when message ordering and queue-level deduplication are important. However, FIFO deduplication has a limited scope and does not eliminate every possible duplicate side effect in downstream systems.
Amazon SNS or Amazon EventBridge can help distribute events to multiple consumers, depending on the routing and filtering requirements of the application.
AWS Step Functions may be a better fit when a workflow involves multiple dependent steps, explicit retry policies, branching, timeouts, and compensation actions.
These services address different architectural needs. The right choice depends on ordering requirements, throughput, failure recovery, and the complexity of the workflow.
Conclusion
Reliable event-driven systems are designed with failure in mind. Partial batch responses prevent one failed message from unnecessarily affecting successful messages. Dead-letter queues isolate messages that cannot be processed, while idempotency protects applications from duplicate operations.
Combined with appropriate monitoring, concurrency controls, and recovery procedures, these practices make SQS-to-Lambda pipelines more predictable and easier to operate in production.
The objective is not to eliminate every failure. It is to ensure that failures are contained, observable, recoverable, and safe to retry.
What resilience patterns have worked best in your serverless applications? Share your experience and lessons learned in the comments..
Any opinions in this article are those of the individual author and may not reflect the opinions of AWS.
Enjoyed reading this content? Let the author know!

Your likes, comments, shares, and saves help creators reach more builders.

Loading recommendations

Loading article