AWS Lambda Advanced Patterns
Lambda's serverless model eliminates server management, but requires understanding cold starts, memory/CPU trade-offs, and architectural patterns.
Cold Start Optimization
# BAD: Import inside handler (runs every cold start AND warm invocation)
def handler(event, context):
import boto3 # Slow!
import json
client = boto3.client('s3')
...
# GOOD: Top-level imports (only on cold start)
import boto3
import json
# Initialize clients outside handler
s3_client = boto3.client('s3')
dynamodb = boto3.resource('dynamodb')
table = dynamodb.Table('my-table')
def handler(event, context):
# s3_client already initialized
response = s3_client.get_object(Bucket='my-bucket', Key='data.json')
return json.loads(response['Body'].read())
Provisioned Concurrency
# serverless.yml / CloudFormation
Resources:
MyFunction:
Type: AWS::Lambda::Function
Properties:
FunctionName: my-api
Runtime: python3.12
Handler: index.handler
Code:
ZipFile: |
def handler(event, context):
return {"statusCode": 200, "body": "Hello"}
FunctionVersion:
Type: AWS::Lambda::Version
Properties:
FunctionName: !Ref MyFunction
ProvisionedConcurrency:
Type: AWS::Lambda::ProvisionedConcurrencyConfig
Properties:
FunctionName: !Ref MyFunction
Qualifier: !GetAtt FunctionVersion.Version
ProvisionedConcurrentExecutions: 10 # Pre-warm 10 instances
Lambda SnapStart (Java)
// Enables faster cold starts for Java by taking snapshots after init
// In AWS SAM template:
// SnapStart:
// ApplyOn: PublishedVersions
@RestController
public class ApiHandler implements RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> {
// These initialize during snapshot (not cold start)
private static final ObjectMapper mapper = new ObjectMapper();
private static final DynamoDbClient dynamoDB = DynamoDbClient.create();
@Override
public APIGatewayProxyResponseEvent handleRequest(
APIGatewayProxyRequestEvent input,
Context context
) {
// Hot path - no initialization needed
String body = processRequest(input.getBody());
return new APIGatewayProxyResponseEvent()
.withStatusCode(200)
.withBody(body);
}
}
Lambda Layers
# Create a layer with dependencies
mkdir -p python/lib/python3.12/site-packages
pip install requests boto3 -t python/lib/python3.12/site-packages/
zip -r my-layer.zip python/
# Publish layer
aws lambda publish-layer-version --layer-name my-dependencies --zip-file fileb://my-layer.zip --compatible-runtimes python3.12
# Attach to function
aws lambda update-function-configuration --function-name my-function --layers arn:aws:lambda:us-east-1:123456789:layer:my-dependencies:1
Lambda Power Tuning
# Use AWS Lambda Power Tuning tool (Step Functions)
# https://github.com/alexcasalboni/aws-lambda-power-tuning
# Input configuration
{
"lambdaARN": "arn:aws:lambda:us-east-1:123:function:my-function",
"powerValues": [128, 256, 512, 1024, 2048, 3008],
"num": 50,
"payload": {"test": "data"},
"parallelInvocation": true,
"strategy": "cost"
}
# Results show optimal memory/cost/speed trade-off
# Often: 512MB is sweet spot between cost and latency
Lambda URLs (Direct HTTPS Endpoint)
// CDK: Create Lambda with Function URL
import * as lambda from 'aws-cdk-lib/aws-lambda';
import * as lambdaNodejs from 'aws-cdk-lib/aws-lambda-nodejs';
const fn = new lambdaNodejs.NodejsFunction(this, 'ApiFunction', {
entry: 'src/handler.ts',
handler: 'handler',
runtime: lambda.Runtime.NODEJS_20_X,
environment: {
TABLE_NAME: table.tableName,
},
});
// Add Function URL
const fnUrl = fn.addFunctionUrl({
authType: lambda.FunctionUrlAuthType.NONE,
cors: {
allowedOrigins: ['https://myapp.com'],
allowedMethods: [lambda.HttpMethod.ALL],
allowedHeaders: ['*'],
},
});
// Handler
export async function handler(event: any) {
const method = event.requestContext.http.method;
const path = event.rawPath;
return {
statusCode: 200,
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ path, method }),
};
}
Event Source Mapping: SQS
// Process SQS messages in batches
export async function sqsHandler(event: SQSEvent): Promise<SQSBatchResponse> {
const failures: SQSBatchItemFailure[] = [];
await Promise.all(
event.Records.map(async (record) => {
try {
const body = JSON.parse(record.body);
await processMessage(body);
} catch (error) {
console.error(`Failed: ${record.messageId}`, error);
failures.push({ itemIdentifier: record.messageId });
}
})
);
// Return partial failures - only failed messages requeued
return { batchItemFailures: failures };
}
// Lambda configuration for SQS trigger
// BatchSize: 10
// MaximumBatchingWindowInSeconds: 30
// FunctionResponseTypes: ReportBatchItemFailures
Dead Letter Queue Pattern
Resources:
ProcessFunction:
Type: AWS::Lambda::Function
Properties:
DeadLetterConfig:
TargetArn: !GetAtt DLQ.Arn
DLQ:
Type: AWS::SQS::Queue
Properties:
QueueName: process-dlq
MessageRetentionPeriod: 1209600 # 14 days
DLQAlarm:
Type: AWS::CloudWatch::Alarm
Properties:
MetricName: ApproximateNumberOfMessagesVisible
Namespace: AWS/SQS
Dimensions:
- Name: QueueName
Value: process-dlq
Threshold: 1
ComparisonOperator: GreaterThanOrEqualToThreshold
AlarmActions:
- !Ref AlertTopic
Summary
Lambda optimization strategies:
- Cold starts: Initialize outside handler, use SnapStart for Java
- Memory: Use power tuning to find optimal memory setting
- Layers: Share code/dependencies across functions
- Provisioned concurrency: Pre-warm for latency-sensitive APIs
- SQS: Use batch item failure reporting for reliable processing
- DLQ: Always configure dead letter queues for error handling