AWS Lambda Performance Optimization
Key concepts
Memory and timeout settings
Cold starts and warm starts
Provisioned concurrency
Lambda layers
VPC configuration impact
Overview
AWS Lambda is a serverless compute service that automatically scales and manages infrastructure. However, performance optimization requires understanding memory allocation, cold start mitigation, and architectural choices. Optimizing Lambda performance is critical for latency-sensitive applications and appears frequently on the SAA-C03 exam.
Core Concept
Lambda performance optimization has three key levers: Memory allocation (also controls CPU, more memory = faster execution but higher cost), Cold start mitigation (Provisioned Concurrency, SnapStart, or keeping functions warm), and Architecture choice (ARM Graviton2 offers 19% better performance at 20% lower cost). Key rule: Memory scales linearly with cost, but execution time may decrease non-linearly.
Exam signals: 'sub-100ms latency required' → Provisioned Concurrency, 'Java cold starts' → SnapStart, 'cost optimization + performance' → ARM Graviton2, 'CPU-intensive workload' → increase memory for more CPU. Remember: more memory = more CPU but higher $/GB-sec cost.
Key Concepts
Memory and CPU Allocation

Memory-CPU-Performance Relationship
Memory allocation directly controls CPU power
CPU Scaling:
- 128 MB to 1,769 MB: CPU scales linearly with memory
- 1,769 MB = 1 full vCPU
- 1,769 MB to 10,240 MB (max): Up to 6 vCPUs
- At 1,769 MB, you get 1 full vCPU for entire duration
- Beyond 1,769 MB, you get multiple vCPUs (multi-threading benefits)
Performance Impact:
Example: CPU-bound image processing task
- 128 MB (0.08 vCPU): 10,000ms execution
- 512 MB (0.3 vCPU): 3,000ms execution
- 1,024 MB (0.6 vCPU): 1,700ms execution
- 1,769 MB (1.0 vCPU): 1,000ms execution
- 3,008 MB (1.7 vCPU): 650ms execution
Cost Formula:
Cost = (Memory in GB) × (Duration in seconds) × Price per GB-second
Price = $0.0000166667 per GB-second
Example comparison:
- 128 MB, 10s: 0.125 × 10 × $0.0000166667 = $0.000021
- 1,024 MB, 1.7s: 1 × 1.7 × $0.0000166667 = $0.000028
- 3,008 MB, 0.65s: 2.94 × 0.65 × $0.0000166667 = $0.000032
Key Insight: Higher memory can REDUCE total cost if execution time decreases proportionally more than memory increases.
Finding the Optimal Memory Configuration
Use AWS Lambda Power Tuning
How It Works:
- Runs function with multiple memory configurations (128MB to 10GB)
- Measures execution time and cost for each
- Generates visualization showing optimal point
- Supports different optimization strategies (cost, speed, balanced)
Typical Optimization Patterns:
CPU-Bound Workloads:
- Image/video processing
- Data transformation
- Cryptographic operations
- Compression/decompression
- Strategy: Increase memory significantly (1,769MB+) for more CPU
I/O-Bound Workloads:
- Database queries
- API calls to external services
- S3 operations
- Strategy: Moderate memory (512-1,024MB), focus on async operations
Memory-Intensive Workloads:
- Large data processing
- ML inference with large models
- In-memory caching
- Strategy: Allocate based on actual memory needs, not CPU needs
# Deploy Lambda Power Tuning via SAM
git clone https://github.com/alexcasalboni/aws-lambda-power-tuning.git
cd aws-lambda-power-tuning
sam deploy --guided
# Run power tuning for your function
aws stepfunctions start-execution \
--state-machine-arn arn:aws:states:us-east-1:123456789012:stateMachine:powerTuningStateMachine \
--input '{
"lambdaARN": "arn:aws:lambda:us-east-1:123456789012:function:my-function",
"powerValues": [128, 256, 512, 1024, 1536, 2048, 3008],
"num": 50,
"payload": {},
"parallelInvocation": true,
"strategy": "balanced"
}'
# Analyze results from Step Functions execution output
# Results include visualization URL and optimal memory recommendation
# Apply optimal memory configuration
aws lambda update-function-configuration \
--function-name my-function \
--memory-size 1536Cold Start Optimization

Understanding Cold Starts
Cold Start = Time to initialize new execution environment
Cold Start Phases:
- Download Code: Lambda retrieves deployment package (~100ms for <50MB)
- Start Runtime: Initialize runtime environment (varies by runtime)
- Initialize Code: Run initialization code outside handler (custom)
- Invoke Handler: First invocation
Cold Start Duration by Runtime (typical): | Runtime | Cold Start Duration | |---------|-------------------| | Python 3.12 | 100-200ms | | Node.js 20 | 150-250ms | | Go | 100-150ms | | Rust | 100-150ms | | Java 21 (no SnapStart) | 2,000-10,000ms | | Java 21 (with SnapStart) | 200-600ms | | .NET 8 | 500-2,000ms |
Factors Affecting Cold Start:
- Runtime choice (compiled vs interpreted)
- Deployment package size
- VPC configuration (adds 1-10 seconds)
- Memory allocation (more memory = faster init)
- Number of dependencies/libraries
Cold Start Mitigation Strategies
1. Provisioned Concurrency
- Pre-initialized execution environments
- Always warm, no cold starts
- Responds in double-digit milliseconds
- Cost: ~$0.0000041667 per GB-second (provisioned)
- Use when: Consistent traffic, latency <100ms required
2. Lambda SnapStart (Java/Python/.NET)
- Creates snapshot after initialization phase
- Restores from snapshot instead of cold start
- Reduces Java cold starts from 6s to <1s (10x faster)
- Available for Java 11/17/21, Python 3.12+, .NET 8
- Cost: No additional cost except snapshot storage ($0.00000309 per GB-month)
- Use when: Java workloads with frequent cold starts
3. Keep Functions Warm
- Scheduled EventBridge rule invokes function every 5 minutes
- Maintains warm execution environment
- Cost: Minimal (just invocation + short duration costs)
- Use when: Budget-constrained, inconsistent traffic
4. Optimize Deployment Package
- Minimize package size (<50MB ideal, <250MB max)
- Remove unnecessary dependencies
- Use Lambda Layers for shared libraries
- Impact: Faster download phase (100ms → 50ms)
5. Optimize Initialization Code
- Move SDK initialization outside handler
- Lazy-load heavy dependencies
- Use connection pooling
- Cache computed values
6. Avoid VPC Unless Required
- VPC adds 1-10 seconds to cold start
- Use VPC only when accessing VPC resources
- Consider NAT Gateway for internet access from Lambda
# Enable Provisioned Concurrency for a function version
aws lambda put-provisioned-concurrency-config \
--function-name my-function \
--qualifier v1 \
--provisioned-concurrent-executions 100
# Configure Auto Scaling for Provisioned Concurrency
aws application-autoscaling register-scalable-target \
--service-namespace lambda \
--resource-id function:my-function:v1 \
--scalable-dimension lambda:function:ProvisionedConcurrency \
--min-capacity 50 \
--max-capacity 200
# Create scaling policy (target tracking)
aws application-autoscaling put-scaling-policy \
--service-namespace lambda \
--resource-id function:my-function:v1 \
--scalable-dimension lambda:function:ProvisionedConcurrency \
--policy-name my-scaling-policy \
--policy-type TargetTrackingScaling \
--target-tracking-scaling-policy-configuration '{
"TargetValue": 0.70,
"PredefinedMetricSpecification": {
"PredefinedMetricType": "LambdaProvisionedConcurrencyUtilization"
}
}'
# Schedule Provisioned Concurrency (business hours only)
# Scale up at 8 AM
aws application-autoscaling put-scheduled-action \
--service-namespace lambda \
--resource-id function:my-function:v1 \
--scalable-dimension lambda:function:ProvisionedConcurrency \
--scheduled-action-name scale-up-morning \
--schedule "cron(0 8 ? * MON-FRI *)" \
--scalable-target-action MinCapacity=100,MaxCapacity=200
# Scale down at 6 PM
aws application-autoscaling put-scheduled-action \
--service-namespace lambda \
--resource-id function:my-function:v1 \
--scalable-dimension lambda:function:ProvisionedConcurrency \
--scheduled-action-name scale-down-evening \
--schedule "cron(0 18 ? * MON-FRI *)" \
--scalable-target-action MinCapacity=0,MaxCapacity=0# Enable SnapStart for Java function
aws lambda update-function-configuration \
--function-name my-java-function \
--snap-start ApplyOn=PublishedVersions
# Publish new version with SnapStart
aws lambda publish-version \
--function-name my-java-function \
--description "Version with SnapStart enabled"
# Priming hook in Java code (CRaC)
import org.crac.Context;
import org.crac.Core;
import org.crac.Resource;
public class MyHandler implements RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent>, Resource {
public MyHandler() {
Core.getGlobalContext().register(this);
}
@Override
public void beforeCheckpoint(Context<? extends Resource> context) {
// Priming logic: preload classes, warm up connections
System.out.println("Priming before snapshot");
// Initialize expensive resources here
warmUpConnections();
preloadClasses();
}
@Override
public void afterRestore(Context<? extends Resource> context) {
System.out.println("Restored from snapshot");
// Re-establish connections if needed
}
// Handler implementation...
}ARM Graviton2 Architecture

AWS Graviton2 for Lambda
ARM-based processors for Lambda functions
Performance Benefits:
- Up to 19% better performance for compute-intensive workloads
- Up to 34% better price-performance overall
- Better for multi-threaded workloads
- Better for I/O-intensive operations
Cost Benefits:
- 20% lower duration charges vs x86
- Same request charges ($0.20 per 1M requests)
- Same free tier (1M requests + 400K GB-seconds)
- Duration: $0.0000133334 per GB-second (vs $0.0000166667 for x86)
Cost Comparison Example:
Workload: 10M requests/month, 512MB, 200ms avg duration
GB-seconds: 10M × 0.5GB × 0.2s = 1,000,000 GB-seconds
x86 Cost:
- Requests: 10M × $0.0000002 = $2.00
- Duration: 1M × $0.0000166667 = $16.67
- Total: $18.67/month
ARM (Graviton2) Cost:
- Requests: 10M × $0.0000002 = $2.00
- Duration: 1M × $0.0000133334 = $13.33
- Total: $15.33/month
- Savings: $3.34/month (18% reduction)
When to Use ARM:
- ✅ CPU-intensive workloads (encoding, compression, encryption)
- ✅ Multi-threaded applications
- ✅ I/O-heavy operations
- ✅ Cost optimization priority
- ⚠️ Requires ARM-compatible dependencies
- ❌ x86-specific native libraries
Migrating to ARM Graviton2
Migration Approaches by Language:
Interpreted Languages (Easy):
- Python: Change architecture setting, no code changes
- Node.js: Change architecture setting, rebuild native modules
- Ruby: Change architecture setting, test dependencies
Compiled Languages (Requires Rebuild):
- Go: Compile with
GOOS=linux GOARCH=arm64 - Rust: Cross-compile for
aarch64-unknown-linux-gnu - Java: Recompile for ARM64 (if native libraries used)
Container Images:
- Rebuild Docker images for
linux/arm64platform - Use multi-architecture images for flexibility
- Test thoroughly before production
Dependencies to Check:
- Native extensions (Python: numpy, pandas, Pillow)
- Database drivers (psycopg2, mysql-connector)
- C/C++ libraries
- OS-specific binaries
Testing Strategy:
- Create ARM version of function
- Run integration tests
- Performance benchmark (compare with x86)
- Gradually shift traffic (weighted aliases)
- Monitor for errors/performance issues
# Check current architecture
aws lambda get-function-configuration \
--function-name my-function \
--query 'Architectures'
# Update function to ARM architecture
aws lambda update-function-configuration \
--function-name my-function \
--architectures arm64
# For container images - build multi-arch
docker buildx build \
--platform linux/amd64,linux/arm64 \
-t my-lambda:latest \
--push .
# Create ARM-specific layer
# Example: Python dependencies for ARM
docker run --rm \
--platform linux/arm64 \
-v "$PWD":/var/task \
public.ecr.aws/lambda/python:3.12 \
pip install -r requirements.txt -t python/
zip -r layer.zip python/
aws lambda publish-layer-version \
--layer-name my-dependencies-arm \
--compatible-runtimes python3.12 \
--compatible-architectures arm64 \
--zip-file fileb://layer.zip
# A/B testing with weighted aliases
# Create alias with 90% x86, 10% ARM
aws lambda create-alias \
--function-name my-function \
--name prod \
--function-version $X86_VERSION \
--routing-config AdditionalVersionWeights={$ARM_VERSION=0.1}
# Monitor performance differences
aws cloudwatch get-metric-statistics \
--namespace AWS/Lambda \
--metric-name Duration \
--dimensions Name=FunctionName,Value=my-function Name=Resource,Value=my-function:$ARM_VERSION \
--start-time 2024-01-01T00:00:00Z \
--end-time 2024-01-31T23:59:59Z \
--period 3600 \
--statistics Average,Maximum,MinimumBest Practices
- Right-Size Memory: Use Lambda Power Tuning to find optimal memory configuration
- Choose Right Runtime: Go/Rust for lowest cold starts, Python/Node.js for balance
- Minimize Package Size: Keep deployment packages <50MB, use layers for dependencies
- Use SnapStart for Java: Reduces Java cold starts from 6s to <1s
- Schedule Provisioned Concurrency: Only provision during peak hours to save cost
- Migrate to ARM: 20% cost savings with minimal migration effort for most workloads
- Avoid VPC Unless Needed: VPC adds significant cold start latency
- Optimize Initialization: Move SDK clients and connections outside handler
- Use Connection Pooling: Reuse database connections across invocations
- Monitor Cold Start Rate: CloudWatch metric
InitDurationtracks cold starts
Common Exam Scenarios
Exam Scenario Decision Guide
| Scenario | Recommended Solution | Key Reasoning |
|---|---|---|
| API requiring <50ms response time | Provisioned Concurrency | Eliminates cold starts, sub-100ms response guaranteed |
| Java Spring Boot function with 6s cold starts | Lambda SnapStart | Reduces Java cold starts to <1s, no provisioning cost |
| CPU-intensive image processing, cost-sensitive | ARM Graviton2 + increase memory | 20% lower cost, more CPU with higher memory |
| Node.js function, variable traffic, budget-constrained | Standard Lambda + keep-warm pattern | EventBridge rule keeps function warm at low cost |
| Python data processing taking 30s at 512MB | Increase to 2048MB | More CPU reduces duration, may lower total cost |
| Java function cold start 10s, consistent traffic | SnapStart + Provisioned Concurrency | SnapStart reduces init time, Provisioned ensures always warm |
| 1000 invocations/minute, each 200ms, optimize cost | ARM Graviton2 | High volume benefits from 20% cost reduction |
| Lambda in VPC has 5s cold starts | Remove VPC or use VPC with NAT Gateway | VPC adds latency, use VPC endpoints or remove if not needed |
Common Pitfalls
Over-Provisioning Memory
Allocating maximum memory (10GB) without testing wastes money. Lambda charges by GB-second, so 10GB costs 78x more than 128MB for same duration. Always benchmark with Lambda Power Tuning before choosing memory size.
24/7 Provisioned Concurrency
Running Provisioned Concurrency around the clock for functions that only need low latency during business hours wastes money. Use scheduled scaling to provision only during peak periods (e.g., 8 AM - 6 PM).
Ignoring ARM Compatibility
Migrating to ARM without checking dependencies can break functions. Native libraries (C/C++ extensions) may not have ARM builds. Always test in staging environment before production migration.
VPC for Everything
Adding Lambda to VPC by default adds 1-10 seconds to cold starts. Only use VPC when accessing VPC-only resources (RDS, ElastiCache). For AWS services (DynamoDB, S3), use VPC endpoints or public endpoints.
Related Services
Quick Reference
Performance Optimization Checklist
| Optimization | Impact | Effort | Cost |
|---|---|---|---|
| Right-size memory | 20-50% faster + cost | Low | Variable |
| ARM Graviton2 | 19% faster + 20% cheaper | Low-Medium | -20% |
| SnapStart (Java) | 10x faster cold starts | Low | ~$0 |
| Provisioned Concurrency | Eliminate cold starts | Low | $$$ |
| Reduce package size | 50-100ms faster download | Medium | $0 |
| Optimize init code | 100-500ms faster | Medium-High | $0 |
| Remove VPC | 1-10s faster cold start | Low | $0 |
Memory-CPU Mapping
| Memory (MB) | vCPU Equivalent | Use Case |
|---|---|---|
| 128 | 0.08 vCPU | Minimal I/O operations |
| 512 | 0.3 vCPU | Light processing |
| 1,024 | 0.6 vCPU | Moderate processing |
| 1,769 | 1.0 vCPU | Full single-threaded CPU |
| 3,008 | 1.7 vCPU | Multi-threaded workloads |
| 10,240 | 6.0 vCPU | Heavy parallel processing |
CLI Quick Reference
# Update memory configuration
aws lambda update-function-configuration \
--function-name my-function \
--memory-size 1536
# Enable ARM architecture
aws lambda update-function-configuration \
--function-name my-function \
--architectures arm64
# Enable SnapStart (Java)
aws lambda update-function-configuration \
--function-name my-java-function \
--snap-start ApplyOn=PublishedVersions
# Configure Provisioned Concurrency
aws lambda put-provisioned-concurrency-config \
--function-name my-function \
--qualifier v1 \
--provisioned-concurrent-executions 50
Test Your Knowledge
A Python Lambda function processes uploaded images (resize, compress). At 512MB, execution takes 3 seconds. The team wants to optimize cost. What should they do FIRST?
A Java Spring Boot Lambda function has 6-second cold starts, affecting user experience. The function receives steady traffic throughout business hours (9 AM - 5 PM). Which solution provides the BEST balance of performance and cost?
An e-commerce company runs a Lambda function behind API Gateway that requires <100ms response time. The function currently uses x86 architecture with 1024MB memory. How can they optimize for BOTH cost and performance?
A Lambda function in a VPC takes 8 seconds for cold starts (5 seconds for VPC ENI setup). The function queries an RDS database. How can cold start time be reduced?
Further Reading
- AWS Lambda Functions Powered by Graviton2
- Optimizing AWS Lambda costs
- Lambda SnapStart Documentation
- Understanding AWS Lambda Cold Starts
- Lambda Power Tuning Tool
Sources: