Your weekly dose of actionable cloud wisdom to start the week right
The Problem
Your Lambda function works perfectly in testing, but in production it’s a performance nightmare. Users wait 3-5 seconds for responses because of cold starts, your costs are spiralling out of control, and you’re questioning whether serverless was the right choice. Meanwhile, other teams have blazing-fast Lambda functions that cost a fraction of yours.
The Solution
Optimise Lambda performance with proven techniques that balance speed and cost. Most performance issues stem from poor memory allocation, cold start problems, and inefficient code patterns that are easily fixable once you know what to look for.
Essential Performance Optimizations:
1. Right-Size Your Memory Allocation
# Use AWS Lambda Power Tuning to find the sweet spot
# Rule of thumb: More memory = faster CPU + potentially lower cost
# Before: 128MB (slowest, often more expensive)
# After: 1024MB (10x faster, often cheaper per request)
import json
import time
def lambda_handler(event, context):
# Heavy computation example
start_time = time.time()
# Your business logic here
result = complex_calculation(event['data'])
execution_time = time.time() - start_time
return {
'statusCode': 200,
'body': json.dumps({
'result': result,
'execution_time_ms': round(execution_time * 1000, 2)
})
}
2. Reduce Cold Start Impact
import boto3
import json
# Move expensive operations outside handler (runs only on cold start)
dynamodb = boto3.resource('dynamodb')
table = dynamodb.Table('my-table')
# Reuse database connections
session = boto3.Session()
s3_client = session.client('s3')
def lambda_handler(event, context):
# This runs on every invocation (keep it light)
try:
# Fast database operation using pre-initialized connection
response = table.get_item(Key={'id': event['id']})
return {
'statusCode': 200,
'body': json.dumps(response['Item'])
}
except Exception as e:
return {
'statusCode': 500,
'body': json.dumps({'error': str(e)})
}
3. Optimise Package Size
# Use Lambda Layers for shared dependencies
# Layer creation example
mkdir python
pip install requests -t python/
zip -r requests-layer.zip python/
# Create layer
aws lambda publish-layer-version \
--layer-name requests-layer \
--zip-file fileb://requests-layer.zip \
--compatible-runtimes python3.9
# In your function, exclude heavy packages from deployment
echo "requests" > requirements-layer.txt
pip install -r requirements.txt -t . --exclude-files requirements-layer.txt
4. Enable Provisioned Concurrency for Critical Functions
# For functions that can't tolerate cold starts
aws lambda put-provisioned-concurrency-config \
--function-name my-critical-function \
--qualifier $LATEST \
--provisioned-concurrency-count 10
# Use with auto-scaling for cost efficiency
aws application-autoscaling register-scalable-target \
--service-namespace lambda \
--scalable-dimension lambda:function:ProvisionedConcurrency \
--resource-id function:my-critical-function:$LATEST \
--min-capacity 5 \
--max-capacity 50
5. Connection Pooling and Caching
import pymysql
import json
from functools import lru_cache
# Database connection reuse
connection = None
def get_db_connection():
global connection
if connection is None or not connection.open:
connection = pymysql.connect(
host='your-rds-endpoint',
user='username',
password='password',
database='your-db',
connect_timeout=5
)
return connection
# In-memory caching for expensive operations
@lru_cache(maxsize=128)
def expensive_calculation(input_data):
# Cached results persist for the lifetime of the container
return complex_operation(input_data)
def lambda_handler(event, context):
# Reuse connection and cached results
db = get_db_connection()
result = expensive_calculation(event['data'])
return {
'statusCode': 200,
'body': json.dumps({'result': result})
}
Performance Monitoring Setup
# Add performance monitoring to your functions
import time
import os
def performance_monitor(func):
def wrapper(*args, **kwargs):
start_time = time.time()
start_memory = get_memory_usage()
result = func(*args, **kwargs)
end_time = time.time()
end_memory = get_memory_usage()
# Log performance metrics
print(f"Execution time: {(end_time - start_time) * 1000:.2f}ms")
print(f"Memory used: {end_memory - start_memory}MB")
print(f"Cold start: {os.environ.get('AWS_LAMBDA_INITIALIZATION_TYPE', 'provisioned-concurrency')}")
return result
return wrapper
@performance_monitor
def lambda_handler(event, context):
# Your function logic here
pass
Why It Matters
- User Experience: Sub-second response times keep users happy
- Cost Efficiency: Optimised functions often cost 50-80% less
- Scalability: Better performance = more concurrent users supported
- Reliability: Faster functions have lower timeout risk
Try This Week
- Measure current performance – Add timing logs to your worst-performing function
- Run AWS Lambda Power Tuning – Find the optimal memory allocation
- Move initialization outside handler – Reduce cold start impact
- Enable X-Ray tracing – Identify performance bottlenecks visually
Quick Win: Memory Optimization Script
# Test different memory settings programmatically
import boto3
import json
def test_memory_configurations(function_name, test_payload):
lambda_client = boto3.client('lambda')
memory_configs = [128, 256, 512, 1024, 1536, 2048, 3008]
results = []
for memory in memory_configs:
# Update function memory
lambda_client.update_function_configuration(
FunctionName=function_name,
MemorySize=memory
)
# Wait for update to complete
lambda_client.get_waiter('function_updated').wait(FunctionName=function_name)
# Test performance
response = lambda_client.invoke(
FunctionName=function_name,
Payload=json.dumps(test_payload)
)
# Parse results
duration = response['ResponseMetadata']['HTTPHeaders'].get('x-amzn-trace-id')
billing_duration = response.get('LogResult', '')
results.append({
'memory': memory,
'duration': duration,
'cost_per_gb_second': memory * 0.0000166667 # Current pricing
})
return results
Common Performance Killers
- Undersized memory: 128MB is rarely optimal for real workloads
- Large deployment packages: Keep under 10MB for faster cold starts
- Database connection per request: Reuse connections aggressively
- Synchronous external calls: Use async where possible
- No monitoring: You can’t optimize what you don’t measure
Advanced Tips
- Use ARM Graviton2 processors: 20% better price-performance for compatible workloads
- Implement circuit breakers: Fail fast when dependencies are slow
- Optimize VPC configuration: VPC cold starts add 10+ seconds
- Consider Step Functions: Break up long-running processes
Pro Tip: AWS Lambda Power Tuning is a free, open-source tool that automatically finds the optimal memory configuration for your function. It can save you hours of manual testing and often reduces costs by 50%+.








