| name | aws-lambda |
| description | [Applies to: **/*] Definitive guidelines for writing secure, performant, and maintainable AWS Lambda functions using modern best practices. |
| source | cursor_mdc |
aws-lambda Best Practices
This guide outlines the definitive best practices for developing AWS Lambda functions. Adhere to these principles to build reliable, cost-effective, and secure serverless applications.
Code Organization and Structure
1. Single Responsibility Principle: Each Lambda function must perform one distinct task. Decompose complex workflows into smaller, focused functions orchestrated by services like AWS Step Functions.
2. Initialize Outside the Handler: Leverage execution environment reuse by initializing SDK clients, database connections, and heavy dependencies in the global scope.
❌ BAD:
import boto3
def handler(event, context):
s3 = boto3.client('s3')
✅ GOOD:
import boto3
s3 = boto3.client('s3')
def handler(event, context):
3. Use Lambda Layers for Shared Code: Keep deployment packages small and promote code reuse for common libraries and dependencies.
4. Configuration via Environment Variables: Never hardcode operational parameters. Use environment variables for dynamic configuration. For sensitive data, use AWS Secrets Manager or AWS Systems Manager Parameter Store.
5. Structured JSON Logging: Output logs in JSON format to CloudWatch. This makes logs easily queryable and analyzable. Use aws-lambda-powertools Logger utility.
❌ BAD:
print(f"Processing event: {event}")
✅ GOOD:
from aws_lambda_powertools import Logger
logger = Logger()
@logger.inject_lambda_context
def handler(event, context):
logger.info("Processing event", event=event)
6. Infrastructure as Code (AWS CDK v2): Define your Lambda functions and their surrounding infrastructure (API Gateway, DynamoDB, IAM roles) using AWS CDK v2. This ensures version control, repeatability, and safe deployments.
✅ GOOD:
from aws_cdk import Stack, aws_lambda as lambda_
from constructs import Construct
class MyLambdaStack(Stack):
def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None:
super().__init__(scope, construct_id, **kwargs)
lambda_.Function(self, "MyHandler",
runtime=lambda_.Runtime.PYTHON_3_12,
handler="index.handler",
code=lambda_.Code.from_asset("lambda"),
environment={
"TABLE_NAME": "MyTable",
},
)
Common Patterns and Anti-patterns
1. Implement Idempotency: Design functions to produce the same result even if invoked multiple times with the same input. Use aws-lambda-powertools for robust idempotency.
❌ BAD:
def handler(event, context):
order_id = event['detail']['orderId']
✅ GOOD:
from aws_lambda_powertools.utilities.idempotency import idempotent
from aws_lambda_powertools.utilities.idempotency.persistence import DynamoDBPersistenceLayer
import os
persistence_layer = DynamoDBPersistenceLayer(table_name=os.environ["IDEMPOTENCY_TABLE"])
@idempotent(persistence_layer=persistence_layer)
def handler(event, context):
order_id = event['detail']['orderId']
2. Stateless Functions: Functions must not rely on mutable local state between invocations. Any mutable data belongs in external, durable storage (e.g., DynamoDB, S3).
3. Orchestrate with Step Functions: For complex, multi-step workflows, use AWS Step Functions to manage state and coordination. Keep individual Lambda functions simple.
Performance Considerations
1. Optimize Memory and CPU: Memory allocation directly impacts CPU. Use the AWS Lambda Power Tuning tool to find the optimal memory configuration for your function.
2. Keep Deployment Packages Small: Minimize cold start times by reducing package size. Use Lambda Layers and only include necessary dependencies.
3. Use Keep-Alive for Persistent Connections: Prevent idle connections from being purged, especially for HTTP clients.
✅ GOOD:
import http.client
import ssl
http_client = http.client.HTTPSConnection("example.com", context=ssl._create_unverified_context())
def handler(event, context):
http_client.request("GET", "/api/data")
response = http_client.getresponse()
Common Pitfalls and Gotchas
1. Over-privileged IAM Roles: Apply the principle of least privilege. Grant only the minimum necessary permissions to your function's execution role.
❌ BAD:
{
"Effect": "Allow",
"Action": "s3:*",
"Resource": "*"
}
✅ GOOD:
{
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:PutObject"
],
"Resource": "arn:aws:s3:::my-specific-bucket/*"
}
2. Lack of Input Validation: Always validate and sanitize all incoming event data to prevent injection attacks and unexpected behavior. Use libraries like Pydantic or aws-lambda-powertools Parser utility.
3. Recursive Invocations: Avoid functions calling themselves directly or indirectly, as this can lead to infinite loops and escalated costs.
4. Unhandled Errors: Ensure all potential errors are caught and handled gracefully. Throw explicit exceptions for unrecoverable issues. Use async/await for asynchronous operations.
Testing Approaches
1. Unit Testing: Focus on testing the core business logic of your function in isolation, mocking AWS service interactions.
2. Integration Testing: Test your function's interactions with actual AWS services. Use ephemeral resources provisioned by CDK.
3. End-to-End Testing: Simulate real-world scenarios, testing the entire flow from event source to final outcome.
4. Local Development and Testing: Utilize tools like AWS SAM CLI or LocalStack to test functions locally before deployment.