Initial commit
This commit is contained in:
182
skills/async-sync-advisor/SKILL.md
Normal file
182
skills/async-sync-advisor/SKILL.md
Normal file
@@ -0,0 +1,182 @@
|
||||
---
|
||||
name: async-sync-advisor
|
||||
description: Guides users on choosing between async and sync patterns for Lambda functions, including when to use tokio, rayon, and spawn_blocking. Activates when users write Lambda handlers with mixed workloads.
|
||||
allowed-tools: Read, Grep
|
||||
version: 1.0.0
|
||||
---
|
||||
|
||||
# Async/Sync Advisor Skill
|
||||
|
||||
You are an expert at choosing the right concurrency pattern for AWS Lambda in Rust. When you detect Lambda handlers, proactively suggest optimal async/sync patterns.
|
||||
|
||||
## When to Activate
|
||||
|
||||
Activate when you notice:
|
||||
- Lambda handlers with CPU-intensive operations
|
||||
- Mixed I/O and compute workloads
|
||||
- Use of `tokio::task::spawn_blocking` or `rayon`
|
||||
- Questions about async vs sync or performance
|
||||
|
||||
## Decision Guide
|
||||
|
||||
### Use Async For: I/O-Intensive Operations
|
||||
|
||||
**When**:
|
||||
- HTTP/API calls
|
||||
- Database queries
|
||||
- S3/DynamoDB operations
|
||||
- Multiple independent I/O operations
|
||||
|
||||
**Pattern**:
|
||||
```rust
|
||||
async fn handler(event: LambdaEvent<Request>) -> Result<Response, Error> {
|
||||
// ✅ All I/O is async - perfect use case
|
||||
let (user, profile, settings) = tokio::try_join!(
|
||||
fetch_user(id),
|
||||
fetch_profile(id),
|
||||
fetch_settings(id),
|
||||
)?;
|
||||
|
||||
Ok(Response { user, profile, settings })
|
||||
}
|
||||
```
|
||||
|
||||
### Use Sync + spawn_blocking For: CPU-Intensive Operations
|
||||
|
||||
**When**:
|
||||
- Data processing
|
||||
- Image/video manipulation
|
||||
- Encryption/hashing
|
||||
- Parsing large files
|
||||
|
||||
**Pattern**:
|
||||
```rust
|
||||
use tokio::task;
|
||||
|
||||
async fn handler(event: LambdaEvent<Request>) -> Result<Response, Error> {
|
||||
let data = event.payload.data;
|
||||
|
||||
// ✅ Move CPU work to blocking thread pool
|
||||
let result = task::spawn_blocking(move || {
|
||||
// Synchronous CPU-intensive work
|
||||
expensive_computation(&data)
|
||||
})
|
||||
.await??;
|
||||
|
||||
Ok(Response { result })
|
||||
}
|
||||
```
|
||||
|
||||
### Use Rayon For: Parallel CPU Work
|
||||
|
||||
**When**:
|
||||
- Processing large collections
|
||||
- Parallel data transformation
|
||||
- CPU-bound operations that can be parallelized
|
||||
|
||||
**Pattern**:
|
||||
```rust
|
||||
use rayon::prelude::*;
|
||||
use tokio::task;
|
||||
|
||||
async fn handler(event: LambdaEvent<Request>) -> Result<Response, Error> {
|
||||
let items = event.payload.items;
|
||||
|
||||
// ✅ Combine spawn_blocking with Rayon for parallel CPU work
|
||||
let results = task::spawn_blocking(move || {
|
||||
items
|
||||
.par_iter()
|
||||
.map(|item| cpu_intensive_work(item))
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
.await?;
|
||||
|
||||
Ok(Response { results })
|
||||
}
|
||||
```
|
||||
|
||||
## Mixed Workload Pattern
|
||||
|
||||
```rust
|
||||
async fn handler(event: LambdaEvent<Request>) -> Result<Response, Error> {
|
||||
// Phase 1: Async I/O - Download data
|
||||
let download_futures = event.payload.urls
|
||||
.into_iter()
|
||||
.map(|url| async move {
|
||||
reqwest::get(&url).await?.bytes().await
|
||||
});
|
||||
let raw_data = futures::future::try_join_all(download_futures).await?;
|
||||
|
||||
// Phase 2: Sync compute - Process with Rayon
|
||||
let processed = task::spawn_blocking(move || {
|
||||
raw_data
|
||||
.par_iter()
|
||||
.map(|bytes| process_data(bytes))
|
||||
.collect::<Result<Vec<_>, _>>()
|
||||
})
|
||||
.await??;
|
||||
|
||||
// Phase 3: Async I/O - Upload results
|
||||
let upload_futures = processed
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(|(i, data)| async move {
|
||||
upload_to_s3(&format!("result-{}.dat", i), &data).await
|
||||
});
|
||||
futures::future::try_join_all(upload_futures).await?;
|
||||
|
||||
Ok(Response { success: true })
|
||||
}
|
||||
```
|
||||
|
||||
## Common Mistakes
|
||||
|
||||
### ❌ Using async for CPU work
|
||||
|
||||
```rust
|
||||
// BAD: Async adds overhead for CPU-bound work
|
||||
async fn handler(event: LambdaEvent<Request>) -> Result<Response, Error> {
|
||||
let result = expensive_cpu_computation(&event.payload.data); // Blocks async runtime
|
||||
Ok(Response { result })
|
||||
}
|
||||
|
||||
// GOOD: Use spawn_blocking
|
||||
async fn handler(event: LambdaEvent<Request>) -> Result<Response, Error> {
|
||||
let data = event.payload.data.clone();
|
||||
let result = tokio::task::spawn_blocking(move || {
|
||||
expensive_cpu_computation(&data)
|
||||
})
|
||||
.await?;
|
||||
Ok(Response { result })
|
||||
}
|
||||
```
|
||||
|
||||
### ❌ Not using concurrency for I/O
|
||||
|
||||
```rust
|
||||
// BAD: Sequential I/O
|
||||
async fn handler(event: LambdaEvent<Request>) -> Result<Response, Error> {
|
||||
let user = fetch_user(id).await?;
|
||||
let posts = fetch_posts(id).await?; // Waits for user first
|
||||
Ok(Response { user, posts })
|
||||
}
|
||||
|
||||
// GOOD: Concurrent I/O
|
||||
async fn handler(event: LambdaEvent<Request>) -> Result<Response, Error> {
|
||||
let (user, posts) = tokio::try_join!(
|
||||
fetch_user(id),
|
||||
fetch_posts(id),
|
||||
)?;
|
||||
Ok(Response { user, posts })
|
||||
}
|
||||
```
|
||||
|
||||
## Your Approach
|
||||
|
||||
When you see Lambda handlers:
|
||||
1. Identify workload type (I/O vs CPU)
|
||||
2. Suggest appropriate pattern (async vs sync)
|
||||
3. Show how to combine patterns for mixed workloads
|
||||
4. Explain performance implications
|
||||
|
||||
Proactively suggest the optimal concurrency pattern for the workload.
|
||||
213
skills/cold-start-optimizer/SKILL.md
Normal file
213
skills/cold-start-optimizer/SKILL.md
Normal file
@@ -0,0 +1,213 @@
|
||||
---
|
||||
name: cold-start-optimizer
|
||||
description: Provides guidance on reducing Lambda cold start times through binary optimization, lazy initialization, and deployment strategies. Activates when users discuss cold starts or deployment configuration.
|
||||
allowed-tools: Read, Grep
|
||||
version: 1.0.0
|
||||
---
|
||||
|
||||
# Cold Start Optimizer Skill
|
||||
|
||||
You are an expert at optimizing AWS Lambda cold starts for Rust functions. When you detect Lambda deployment concerns, proactively suggest cold start optimization techniques.
|
||||
|
||||
## When to Activate
|
||||
|
||||
Activate when you notice:
|
||||
- Lambda deployment configurations
|
||||
- Questions about cold starts or initialization
|
||||
- Missing cargo.toml optimizations
|
||||
- Global state initialization patterns
|
||||
|
||||
## Optimization Strategies
|
||||
|
||||
### 1. Binary Size Reduction
|
||||
|
||||
**Cargo.toml Configuration**:
|
||||
```toml
|
||||
[profile.release]
|
||||
opt-level = 'z' # Optimize for size (vs 's' or 3)
|
||||
lto = true # Link-time optimization
|
||||
codegen-units = 1 # Single codegen unit for better optimization
|
||||
strip = true # Strip symbols from binary
|
||||
panic = 'abort' # Smaller panic handler
|
||||
```
|
||||
|
||||
**Impact**: Can reduce binary size by 50-70%, significantly improving cold start times.
|
||||
|
||||
### 2. Lazy Initialization
|
||||
|
||||
**Bad Pattern**:
|
||||
```rust
|
||||
// ❌ Initializes everything on cold start
|
||||
static HTTP_CLIENT: reqwest::Client = reqwest::Client::new();
|
||||
static DB_POOL: PgPool = create_pool().await; // Won't even compile
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Error> {
|
||||
// Heavy initialization before handler is ready
|
||||
tracing_subscriber::fmt().init();
|
||||
init_aws_sdk().await;
|
||||
warm_cache().await;
|
||||
|
||||
run(service_fn(handler)).await
|
||||
}
|
||||
```
|
||||
|
||||
**Good Pattern**:
|
||||
```rust
|
||||
use std::sync::OnceLock;
|
||||
|
||||
// ✅ Lazy initialization - only creates when first used
|
||||
static HTTP_CLIENT: OnceLock<reqwest::Client> = OnceLock::new();
|
||||
|
||||
fn get_client() -> &'static reqwest::Client {
|
||||
HTTP_CLIENT.get_or_init(|| {
|
||||
reqwest::Client::builder()
|
||||
.timeout(Duration::from_secs(10))
|
||||
.build()
|
||||
.unwrap()
|
||||
})
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Error> {
|
||||
// Minimal initialization
|
||||
tracing_subscriber::fmt()
|
||||
.without_time()
|
||||
.init();
|
||||
|
||||
run(service_fn(handler)).await
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Dependency Optimization
|
||||
|
||||
**Audit Dependencies**:
|
||||
```bash
|
||||
cargo tree
|
||||
cargo bloat --release
|
||||
```
|
||||
|
||||
**Reduce Features**:
|
||||
```toml
|
||||
[dependencies]
|
||||
# ❌ BAD: Pulls in everything
|
||||
tokio = "1"
|
||||
|
||||
# ✅ GOOD: Only what you need
|
||||
tokio = { version = "1", features = ["rt-multi-thread", "macros"] }
|
||||
|
||||
# ✅ Disable default features when possible
|
||||
serde = { version = "1", default-features = false, features = ["derive"] }
|
||||
```
|
||||
|
||||
### 4. ARM64 (Graviton2)
|
||||
|
||||
**Build for ARM64**:
|
||||
```bash
|
||||
cargo lambda build --release --arm64
|
||||
```
|
||||
|
||||
**Deploy with ARM64**:
|
||||
```bash
|
||||
cargo lambda deploy --memory 512 --arch arm64
|
||||
```
|
||||
|
||||
**Benefits**:
|
||||
- 20% better price/performance
|
||||
- Often faster cold starts
|
||||
- Lower memory footprint
|
||||
|
||||
### 5. Provisioned Concurrency
|
||||
|
||||
For critical functions with strict latency requirements:
|
||||
|
||||
```bash
|
||||
# CloudFormation/SAM
|
||||
ProvisionedConcurrencyConfig:
|
||||
ProvisionedConcurrentExecutions: 2
|
||||
|
||||
# Or via AWS CLI
|
||||
aws lambda put-provisioned-concurrency-config \
|
||||
--function-name my-function \
|
||||
--provisioned-concurrent-executions 2
|
||||
```
|
||||
|
||||
**Trade-off**: Costs more but eliminates cold starts.
|
||||
|
||||
## Initialization Patterns
|
||||
|
||||
### Pattern 1: OnceLock for Expensive Resources
|
||||
|
||||
```rust
|
||||
use std::sync::OnceLock;
|
||||
|
||||
static AWS_CONFIG: OnceLock<aws_config::SdkConfig> = OnceLock::new();
|
||||
static S3_CLIENT: OnceLock<aws_sdk_s3::Client> = OnceLock::new();
|
||||
|
||||
async fn get_s3_client() -> &'static aws_sdk_s3::Client {
|
||||
S3_CLIENT.get_or_init(|| {
|
||||
let config = AWS_CONFIG.get_or_init(|| {
|
||||
tokio::runtime::Handle::current()
|
||||
.block_on(aws_config::load_from_env())
|
||||
});
|
||||
aws_sdk_s3::Client::new(config)
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
### Pattern 2: Conditional Initialization
|
||||
|
||||
```rust
|
||||
async fn handler(event: LambdaEvent<Request>) -> Result<Response, Error> {
|
||||
// Only initialize if needed
|
||||
let client = if event.payload.needs_api_call {
|
||||
Some(get_http_client())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// Process without client if not needed
|
||||
process(event.payload, client).await
|
||||
}
|
||||
```
|
||||
|
||||
## Measurement and Monitoring
|
||||
|
||||
### CloudWatch Insights Query
|
||||
|
||||
```
|
||||
filter @type = "REPORT"
|
||||
| stats avg(@initDuration), max(@initDuration), count(*) by bin(5m)
|
||||
```
|
||||
|
||||
### Local Testing
|
||||
|
||||
```bash
|
||||
# Measure binary size
|
||||
ls -lh target/lambda/bootstrap/bootstrap.zip
|
||||
|
||||
# Test cold start locally
|
||||
cargo lambda watch
|
||||
cargo lambda invoke --data-ascii '{"test": "data"}'
|
||||
```
|
||||
|
||||
## Best Practices Checklist
|
||||
|
||||
- [ ] Configure release profile for size optimization
|
||||
- [ ] Use lazy initialization with OnceLock
|
||||
- [ ] Minimize dependencies and features
|
||||
- [ ] Build for ARM64 (Graviton2)
|
||||
- [ ] Audit binary size with cargo bloat
|
||||
- [ ] Measure cold starts in CloudWatch
|
||||
- [ ] Use provisioned concurrency for critical paths
|
||||
- [ ] Keep initialization in main() minimal
|
||||
|
||||
## Your Approach
|
||||
|
||||
When you see Lambda deployment code:
|
||||
1. Check Cargo.toml for optimization settings
|
||||
2. Look for eager initialization that could be lazy
|
||||
3. Suggest ARM64 deployment
|
||||
4. Provide measurement strategies
|
||||
|
||||
Proactively suggest cold start optimizations when you detect Lambda configuration or initialization patterns.
|
||||
168
skills/lambda-optimization-advisor/SKILL.md
Normal file
168
skills/lambda-optimization-advisor/SKILL.md
Normal file
@@ -0,0 +1,168 @@
|
||||
---
|
||||
name: lambda-optimization-advisor
|
||||
description: Reviews AWS Lambda functions for performance, memory configuration, and cost optimization. Activates when users write Lambda handlers or discuss Lambda performance.
|
||||
allowed-tools: Read, Grep, Glob
|
||||
version: 1.0.0
|
||||
---
|
||||
|
||||
# Lambda Optimization Advisor Skill
|
||||
|
||||
You are an expert at optimizing AWS Lambda functions written in Rust. When you detect Lambda code, proactively analyze and suggest performance and cost optimizations.
|
||||
|
||||
## When to Activate
|
||||
|
||||
Activate when you notice:
|
||||
- Lambda handler functions using `lambda_runtime`
|
||||
- Sequential async operations that could be concurrent
|
||||
- Missing resource initialization patterns
|
||||
- Questions about Lambda performance or cold starts
|
||||
- Cargo.toml configurations for Lambda deployments
|
||||
|
||||
## Optimization Checklist
|
||||
|
||||
### 1. Concurrent Operations
|
||||
|
||||
**What to Look For**: Sequential async operations
|
||||
|
||||
**Bad Pattern**:
|
||||
```rust
|
||||
async fn handler(event: LambdaEvent<Request>) -> Result<Response, Error> {
|
||||
// ❌ Sequential: takes 3+ seconds total
|
||||
let user = fetch_user(&event.payload.user_id).await?;
|
||||
let posts = fetch_posts(&event.payload.user_id).await?;
|
||||
let comments = fetch_comments(&event.payload.user_id).await?;
|
||||
|
||||
Ok(Response { user, posts, comments })
|
||||
}
|
||||
```
|
||||
|
||||
**Good Pattern**:
|
||||
```rust
|
||||
async fn handler(event: LambdaEvent<Request>) -> Result<Response, Error> {
|
||||
// ✅ Concurrent: all three requests happen simultaneously
|
||||
let (user, posts, comments) = tokio::try_join!(
|
||||
fetch_user(&event.payload.user_id),
|
||||
fetch_posts(&event.payload.user_id),
|
||||
fetch_comments(&event.payload.user_id),
|
||||
)?;
|
||||
|
||||
Ok(Response { user, posts, comments })
|
||||
}
|
||||
```
|
||||
|
||||
**Suggestion**: Use `tokio::join!` or `tokio::try_join!` for concurrent operations. This can reduce execution time by 3-5x for I/O-bound workloads.
|
||||
|
||||
### 2. Resource Initialization
|
||||
|
||||
**What to Look For**: Creating clients inside the handler
|
||||
|
||||
**Bad Pattern**:
|
||||
```rust
|
||||
async fn handler(event: LambdaEvent<Request>) -> Result<Response, Error> {
|
||||
// ❌ Creates new client for every invocation
|
||||
let client = reqwest::Client::new();
|
||||
let data = client.get("https://api.example.com").await?;
|
||||
Ok(Response { data })
|
||||
}
|
||||
```
|
||||
|
||||
**Good Pattern**:
|
||||
```rust
|
||||
use std::sync::OnceLock;
|
||||
|
||||
// ✅ Initialized once per container (reused across invocations)
|
||||
static HTTP_CLIENT: OnceLock<reqwest::Client> = OnceLock::new();
|
||||
|
||||
async fn handler(event: LambdaEvent<Request>) -> Result<Response, Error> {
|
||||
let client = HTTP_CLIENT.get_or_init(|| {
|
||||
reqwest::Client::builder()
|
||||
.timeout(Duration::from_secs(10))
|
||||
.build()
|
||||
.unwrap()
|
||||
});
|
||||
|
||||
let data = client.get("https://api.example.com").await?;
|
||||
Ok(Response { data })
|
||||
}
|
||||
```
|
||||
|
||||
**Suggestion**: Use `OnceLock` for expensive resources (HTTP clients, database pools, AWS SDK clients) that should be initialized once and reused.
|
||||
|
||||
### 3. Binary Size Optimization
|
||||
|
||||
**What to Look For**: Missing release profile optimizations
|
||||
|
||||
**Check Cargo.toml**:
|
||||
```toml
|
||||
[profile.release]
|
||||
opt-level = 'z' # ✅ Optimize for size
|
||||
lto = true # ✅ Link-time optimization
|
||||
codegen-units = 1 # ✅ Better optimization
|
||||
strip = true # ✅ Strip symbols
|
||||
panic = 'abort' # ✅ Smaller panic handler
|
||||
```
|
||||
|
||||
**Suggestion**: Configure release profile for smaller binaries. Smaller binaries = faster cold starts and lower storage costs.
|
||||
|
||||
### 4. ARM64 (Graviton2) Usage
|
||||
|
||||
**What to Look For**: Building for x86_64 only
|
||||
|
||||
**Build Command**:
|
||||
```bash
|
||||
# ✅ Build for ARM64 (20% better price/performance)
|
||||
cargo lambda build --release --arm64
|
||||
```
|
||||
|
||||
**Suggestion**: Use ARM64 for 20% better price/performance and often faster cold starts.
|
||||
|
||||
### 5. Memory Configuration
|
||||
|
||||
**What to Look For**: Default memory settings
|
||||
|
||||
**Guidelines**:
|
||||
```bash
|
||||
# Test different memory configs
|
||||
cargo lambda deploy --memory 512 # For simple functions
|
||||
cargo lambda deploy --memory 1024 # For standard workloads
|
||||
cargo lambda deploy --memory 2048 # For CPU-intensive tasks
|
||||
```
|
||||
|
||||
**Suggestion**: Lambda allocates CPU proportionally to memory. For CPU-bound tasks, increasing memory can reduce execution time and total cost.
|
||||
|
||||
## Cost Optimization Patterns
|
||||
|
||||
### Pattern 1: Batch Processing
|
||||
|
||||
```rust
|
||||
async fn handler(event: LambdaEvent<Vec<Item>>) -> Result<(), Error> {
|
||||
// Process multiple items in one invocation
|
||||
let futures = event.payload.iter().map(|item| process_item(item));
|
||||
futures::future::try_join_all(futures).await?;
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
### Pattern 2: Early Return
|
||||
|
||||
```rust
|
||||
async fn handler(event: LambdaEvent<Request>) -> Result<Response, Error> {
|
||||
// ✅ Validate early, fail fast
|
||||
if event.payload.user_id.is_empty() {
|
||||
return Err(Error::from("user_id required"));
|
||||
}
|
||||
|
||||
// Expensive operations only if validation passes
|
||||
let user = fetch_user(&event.payload.user_id).await?;
|
||||
Ok(Response { user })
|
||||
}
|
||||
```
|
||||
|
||||
## Your Approach
|
||||
|
||||
1. **Detect**: Identify Lambda handler code
|
||||
2. **Analyze**: Check for concurrent operations, resource init, config
|
||||
3. **Suggest**: Provide specific optimizations with code examples
|
||||
4. **Explain**: Impact on performance and cost
|
||||
|
||||
Proactively suggest optimizations that will reduce Lambda execution time and costs.
|
||||
Reference in New Issue
Block a user