268 lines
6.8 KiB
Markdown
268 lines
6.8 KiB
Markdown
---
|
||
description: Deep dive into Lambda cost optimization strategies for Rust functions
|
||
---
|
||
|
||
You are helping the user optimize the cost of their Rust Lambda functions.
|
||
|
||
## Your Task
|
||
|
||
Guide the user through advanced cost optimization techniques using AWS Lambda Power Tuning, memory configuration, and Rust-specific optimizations.
|
||
|
||
## Lambda Pricing Model
|
||
|
||
**Cost = (Requests × $0.20 per 1M) + (GB-seconds × $0.0000166667)**
|
||
|
||
- **Requests**: $0.20 per 1 million requests
|
||
- **Duration**: Charged per GB-second
|
||
- 1 GB-second = 1 GB memory × 1 second execution
|
||
- ARM64 (Graviton2): 20% cheaper than x86_64
|
||
|
||
**Example**:
|
||
- 512 MB, 100ms execution, 1M requests/month
|
||
- Duration: 0.5 GB × 0.1s × 1M = 50,000 GB-seconds
|
||
- Cost: $0.20 + (50,000 × $0.0000166667) = $0.20 + $0.83 = $1.03
|
||
|
||
## Memory vs CPU Allocation
|
||
|
||
Lambda allocates CPU proportional to memory:
|
||
- **128 MB**: 0.08 vCPU
|
||
- **512 MB**: 0.33 vCPU
|
||
- **1024 MB**: 0.58 vCPU
|
||
- **1769 MB**: 1.00 vCPU (full core)
|
||
- **3008 MB**: 1.77 vCPU
|
||
- **10240 MB**: 6.00 vCPU
|
||
|
||
**Key insight**: More memory = more CPU = faster execution = potentially lower cost
|
||
|
||
## AWS Lambda Power Tuning Tool
|
||
|
||
Automatically finds optimal memory configuration.
|
||
|
||
### Setup
|
||
|
||
```bash
|
||
# Deploy Power Tuning (one-time)
|
||
git clone https://github.com/alexcasalboni/aws-lambda-power-tuning
|
||
cd aws-lambda-power-tuning
|
||
sam deploy --guided
|
||
|
||
# Run power tuning
|
||
aws stepfunctions start-execution \
|
||
--state-machine-arn arn:aws:states:REGION:ACCOUNT:stateMachine:powerTuningStateMachine \
|
||
--input '{
|
||
"lambdaARN": "arn:aws:lambda:REGION:ACCOUNT:function:my-rust-function",
|
||
"powerValues": [128, 256, 512, 1024, 1536, 2048, 3008],
|
||
"num": 10,
|
||
"payload": "{\"test\": \"data\"}",
|
||
"parallelInvocation": true,
|
||
"strategy": "cost"
|
||
}'
|
||
```
|
||
|
||
**Strategies**:
|
||
- `cost`: Minimize cost
|
||
- `speed`: Minimize duration
|
||
- `balanced`: Balance cost and speed
|
||
|
||
## Rust-Specific Optimizations
|
||
|
||
### 1. Binary Size Reduction
|
||
|
||
**Cargo.toml**:
|
||
```toml
|
||
[profile.release]
|
||
opt-level = 'z' # Optimize for size
|
||
lto = true # Link-time optimization
|
||
codegen-units = 1 # Single codegen unit
|
||
strip = true # Strip symbols
|
||
panic = 'abort' # Smaller panic handler
|
||
|
||
[profile.release.package."*"]
|
||
opt-level = 'z' # Optimize dependencies too
|
||
```
|
||
|
||
**Result**: 3-5x smaller binary = faster cold starts = lower duration
|
||
|
||
### 2. Use ARM64 (Graviton2)
|
||
|
||
```bash
|
||
cargo lambda build --release --arm64
|
||
cargo lambda deploy --arch arm64
|
||
```
|
||
|
||
**Savings**: 20% lower cost for same performance
|
||
|
||
### 3. Dependency Optimization
|
||
|
||
```bash
|
||
# Analyze binary size
|
||
cargo install cargo-bloat
|
||
cargo bloat --release -n 20
|
||
|
||
# Find unused features
|
||
cargo install cargo-unused-features
|
||
cargo unused-features
|
||
|
||
# Remove unused dependencies
|
||
cargo install cargo-udeps
|
||
cargo +nightly udeps
|
||
```
|
||
|
||
**Example**:
|
||
```toml
|
||
# ❌ Full tokio (heavy)
|
||
tokio = { version = "1", features = ["full"] }
|
||
|
||
# ✅ Only needed features (light)
|
||
tokio = { version = "1", features = ["macros", "rt"] }
|
||
```
|
||
|
||
### 4. Use Lightweight Alternatives
|
||
|
||
- `ureq` instead of `reqwest` for simple HTTP
|
||
- `rustls` instead of `native-tls`
|
||
- `simdjson` instead of `serde_json` for large JSON
|
||
- Avoid `regex` for simple string operations
|
||
|
||
## Memory Configuration Strategies
|
||
|
||
### Strategy 1: Start Low, Test Up
|
||
|
||
```bash
|
||
# Test different memory sizes
|
||
for mem in 128 256 512 1024 2048; do
|
||
echo "Testing ${mem}MB"
|
||
cargo lambda deploy --memory $mem
|
||
# Run load test
|
||
# Measure duration and cost
|
||
done
|
||
```
|
||
|
||
### Strategy 2: Monitor CloudWatch Metrics
|
||
|
||
```bash
|
||
# Get duration statistics
|
||
aws cloudwatch get-metric-statistics \
|
||
--namespace AWS/Lambda \
|
||
--metric-name Duration \
|
||
--dimensions Name=FunctionName,Value=my-function \
|
||
--start-time 2025-01-01T00:00:00Z \
|
||
--end-time 2025-01-02T00:00:00Z \
|
||
--period 3600 \
|
||
--statistics Average,Maximum
|
||
|
||
# Get memory usage
|
||
aws cloudwatch get-metric-statistics \
|
||
--namespace AWS/Lambda \
|
||
--metric-name MemoryUtilization \
|
||
--dimensions Name=FunctionName,Value=my-function \
|
||
--start-time 2025-01-01T00:00:00Z \
|
||
--end-time 2025-01-02T00:00:00Z \
|
||
--period 3600 \
|
||
--statistics Maximum
|
||
```
|
||
|
||
### Strategy 3: Right-size Based on Workload
|
||
|
||
**IO-intensive** (API calls, DB queries):
|
||
- Start: 512 MB
|
||
- Sweet spot: Usually 512-1024 MB
|
||
- Reason: Limited by network, not CPU
|
||
|
||
**Compute-intensive** (data processing):
|
||
- Start: 1024 MB
|
||
- Sweet spot: Usually 1769-3008 MB
|
||
- Reason: More CPU = faster = lower total cost
|
||
|
||
**Mixed workload**:
|
||
- Start: 1024 MB
|
||
- Test: 1024, 1769, 2048 MB
|
||
- Use Power Tuning tool
|
||
|
||
## Cost Optimization Checklist
|
||
|
||
- [ ] Use ARM64 architecture (20% savings)
|
||
- [ ] Optimize binary size (faster cold starts)
|
||
- [ ] Remove unused dependencies
|
||
- [ ] Use lightweight alternatives
|
||
- [ ] Run AWS Lambda Power Tuning
|
||
- [ ] Right-size memory based on workload
|
||
- [ ] Set appropriate timeout (not too high)
|
||
- [ ] Reduce cold starts (keep functions warm if needed)
|
||
- [ ] Use reserved concurrency for predictable workloads
|
||
- [ ] Batch requests when possible
|
||
- [ ] Cache results (DynamoDB, ElastiCache)
|
||
- [ ] Monitor and alert on cost anomalies
|
||
|
||
## Advanced: Provisioned Concurrency
|
||
|
||
For latency-sensitive functions, pre-warm instances.
|
||
|
||
**Cost**: $0.015 per GB-hour (expensive!)
|
||
|
||
```bash
|
||
aws lambda put-provisioned-concurrency-config \
|
||
--function-name my-function \
|
||
--provisioned-concurrent-executions 5
|
||
```
|
||
|
||
**Use when**:
|
||
- Cold starts are unacceptable
|
||
- Predictable traffic patterns
|
||
- Cost justifies latency improvement
|
||
|
||
## Cost Monitoring
|
||
|
||
### CloudWatch Billing Alerts
|
||
|
||
```bash
|
||
aws cloudwatch put-metric-alarm \
|
||
--alarm-name lambda-cost-alert \
|
||
--alarm-description "Alert when Lambda costs exceed threshold" \
|
||
--metric-name EstimatedCharges \
|
||
--namespace AWS/Billing \
|
||
--statistic Maximum \
|
||
--period 21600 \
|
||
--evaluation-periods 1 \
|
||
--threshold 100 \
|
||
--comparison-operator GreaterThanThreshold
|
||
```
|
||
|
||
### Cost Explorer Tags
|
||
|
||
```bash
|
||
cargo lambda deploy \
|
||
--tags Environment=production,Team=backend,CostCenter=engineering
|
||
```
|
||
|
||
## Real-World Optimization Example
|
||
|
||
**Before**:
|
||
- Memory: 128 MB
|
||
- Duration: 2000ms
|
||
- Requests: 10M/month
|
||
- Cost: $0.20 + (0.128 GB × 2s × 10M × $0.0000166667) = $42.87/month
|
||
|
||
**After Optimization**:
|
||
- Binary size: 8MB → 2MB (cold start: 800ms → 300ms)
|
||
- Architecture: x86_64 → ARM64 (20% cheaper)
|
||
- Memory: 128 MB → 512 MB (duration: 2000ms → 600ms)
|
||
- Duration improvement: More CPU = faster execution
|
||
|
||
**Cost calculation**:
|
||
- Compute: 0.512 GB × 0.6s × 10M × $0.0000166667 × 0.8 (ARM discount) = $40.96
|
||
- Requests: $0.20
|
||
- **Total**: $41.16/month (4% savings with better performance!)
|
||
|
||
**Key lesson**: Sometimes more memory = lower total cost due to faster execution.
|
||
|
||
## Rust Performance Advantage
|
||
|
||
Rust vs Python/Node.js:
|
||
- **3-4x cheaper** on average
|
||
- **3-10x faster execution**
|
||
- **2-3x faster cold starts**
|
||
- **Lower memory usage**
|
||
|
||
Guide the user through cost optimization based on their workload and budget constraints.
|