aws-optimization-sst
SKILL.md
AWS Optimization with SST
This skill covers best practices for optimizing AWS resources using SST, focusing on performance, cost, and developer experience.
Core Principles
- Right-size resources: Don't over-provision
- Use SST's defaults: They're already optimized
- Leverage caching: Reduce redundant work
- Optimize cold starts: Minimize Lambda initialization time
- Monitor and iterate: Use data to guide optimization
Lambda Function Optimization
Pattern 1: Function Configuration
// sst.config.ts
new sst.aws.Function("Api", {
handler: "src/api.handler",
memory: "512 MB", // Start here, adjust based on metrics
timeout: "30 seconds", // Don't use default 3 seconds
architecture: "arm64", // 20% cheaper and often faster
nodejs: {
esbuild: {
minify: true, // Smaller bundle
external: [ // Don't bundle AWS SDK v3
"@aws-sdk/*"
]
}
}
});
Memory considerations:
- Start with 512 MB
- Monitor execution time vs cost
- More memory = more CPU = faster execution
- Sometimes higher memory is cheaper (finishes faster)
Architecture:
- Use
arm64(Graviton2) for 20% cost savings - Same or better performance
- Works for most workloads
Pattern 2: Bundle Size Optimization
// Optimize imports - tree-shaking friendly
✅ import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
❌ import * as AWS from "aws-sdk";
// Use specific SDK clients
✅ import { GetCommand } from "@aws-sdk/lib-dynamodb";
❌ import { DocumentClient } from "aws-sdk/clients/dynamodb";
Bundle size tips:
- Use AWS SDK v3 (modular)
- Import only what you need
- Mark heavy deps as external
- Use dynamic imports for large libraries
// Dynamic import for rarely-used code
export async function generatePDF(data: Data) {
const puppeteer = await import("puppeteer");
// Only loads when actually called
}
Pattern 3: Connection Reuse
// ✅ Initialize outside handler (reused across invocations)
import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
const client = new DynamoDBClient({
maxAttempts: 3,
requestHandler: {
connectionTimeout: 3000,
socketTimeout: 3000
}
});
export async function handler(event) {
// Use client here
}
// ❌ Don't initialize inside handler
export async function handler(event) {
const client = new DynamoDBClient({});
// Creates new connection every time
}
Pattern 4: Provisioned Concurrency
For consistently high traffic:
new sst.aws.Function("HighTraffic", {
handler: "src/api.handler",
transform: {
function: {
reservedConcurrentExecutions: 10,
// Provisioned concurrency keeps instances warm
}
}
});
When to use:
- Consistent traffic patterns
- Latency-sensitive applications
- Cost justified by reduced cold starts
DynamoDB Optimization
Pattern 1: Table Configuration
const table = new sst.aws.Dynamo("Database", {
fields: {
pk: "string",
sk: "string"
},
primaryIndex: { hashKey: "pk", rangeKey: "sk" },
// Use on-demand for variable traffic
// Use provisioned for predictable traffic
stream: "new-and-old-images", // Only if needed for triggers
transform: {
table: {
// Enable point-in-time recovery for production
pointInTimeRecovery: $app.stage === "production"
? { enabled: true }
: undefined,
// TTL for automatic data expiration
timeToLiveAttribute: "expiresAt"
}
}
});
Pattern 2: Query Optimization
// ✅ Use Query with specific partition key
await client.send(new QueryCommand({
TableName: Resource.Database.name,
KeyConditionExpression: "pk = :pk",
ExpressionAttributeValues: { ":pk": "USER#123" }
}));
// ❌ Don't use Scan unless absolutely necessary
await client.send(new ScanCommand({
TableName: Resource.Database.name
}));
// Scans entire table - expensive and slow!
Pattern 3: Batch Operations
// Write multiple items efficiently
import { BatchWriteCommand } from "@aws-sdk/lib-dynamodb";
// Batch up to 25 items per request
const batches = chunk(items, 25);
for (const batch of batches) {
await client.send(new BatchWriteCommand({
RequestItems: {
[Resource.Database.name]: batch.map(item => ({
PutRequest: { Item: item }
}))
}
}));
}
Pattern 4: Projection Expressions
// Only fetch fields you need
await client.send(new GetCommand({
TableName: Resource.Database.name,
Key: { pk: "USER#123", sk: "PROFILE" },
ProjectionExpression: "name, email" // Don't fetch everything
}));
Pattern 5: GSI Design
// Sparse indexes save cost
const table = new sst.aws.Dynamo("Database", {
fields: {
pk: "string",
sk: "string",
gsi1pk: "string", // Only set on items that need indexing
gsi1sk: "string"
},
primaryIndex: { hashKey: "pk", rangeKey: "sk" },
globalIndexes: {
gsi1: {
hashKey: "gsi1pk",
rangeKey: "gsi1sk",
projection: "keys_only" // Cheapest option
}
}
});
// Only active users have gsi1pk
{
pk: "USER#123",
sk: "PROFILE",
status: "active",
gsi1pk: "ACTIVE#USER", // Only active users
gsi1sk: "USER#123"
}
S3 Optimization
Pattern 1: Bucket Configuration
const bucket = new sst.aws.Bucket("Uploads", {
transform: {
bucket: {
// Lifecycle rules for cost savings
lifecycleConfiguration: {
rules: [
{
id: "archive-old-files",
status: "Enabled",
transitions: [
{
days: 30,
storageClass: "INTELLIGENT_TIERING"
},
{
days: 90,
storageClass: "GLACIER"
}
]
},
{
id: "delete-temp-files",
status: "Enabled",
expiration: { days: 7 },
filter: {
prefix: "temp/"
}
}
]
}
}
}
});
Pattern 2: Intelligent Tiering
// Automatically moves objects between access tiers
{
storageClass: "INTELLIGENT_TIERING"
}
// Tiers:
// - Frequent Access (default)
// - Infrequent Access (30 days)
// - Archive Instant Access (90 days)
// - Archive Access (90+ days)
// - Deep Archive Access (180+ days)
Pattern 3: CloudFront for Static Assets
const cdn = new sst.aws.Router("CDN", {
routes: {
"/*": {
bucket: bucket
}
},
transform: {
distribution: {
defaultCacheBehavior: {
compress: true, // Enable compression
viewerProtocolPolicy: "redirect-to-https",
cachePolicyId: "658327ea-f89d-4fab-a63d-7e88639e58f6" // CachingOptimized
}
}
}
});
Pattern 4: Presigned URLs
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
import { GetObjectCommand } from "@aws-sdk/client-s3";
// Generate time-limited URL
const url = await getSignedUrl(
s3Client,
new GetObjectCommand({
Bucket: Resource.Uploads.name,
Key: fileKey
}),
{ expiresIn: 3600 } // 1 hour
);
// No Lambda invocation needed for downloads
API Gateway Optimization
Pattern 1: HTTP API vs REST API
// Use HTTP API (cheaper, faster)
new sst.aws.ApiGatewayV2("Api", {
routes: {
"GET /posts": "src/posts.list",
"POST /posts": "src/posts.create"
}
});
// HTTP API is 71% cheaper than REST API
// Same features for most use cases
Pattern 2: Response Caching
new sst.aws.ApiGatewayV2("Api", {
routes: {
"GET /posts": {
function: "src/posts.list",
// Cache at API Gateway level
cache: {
ttl: "5 minutes"
}
}
}
});
Pattern 3: Request Validation
// Reject invalid requests early (before Lambda invocation)
new sst.aws.ApiGatewayV2("Api", {
routes: {
"POST /posts": {
function: "src/posts.create",
authorizer: "iam",
// Validate request before invoking Lambda
}
}
});
Remix Optimization with SST
Pattern 1: Server Bundle Optimization
// remix.config.js
export default {
serverBuildPath: "build/server/index.mjs",
serverMinify: true,
serverModuleFormat: "esm",
// Don't bundle Node.js built-ins
serverDependenciesToBundle: [
/^(?!node:)/, // Bundle everything except node: imports
]
};
Pattern 2: Asset Optimization
const remix = new sst.aws.Remix("Web", {
environment: {
ASSET_URL: cdn.url // Serve assets from CloudFront
},
transform: {
server: {
// Optimize Lambda
memory: "512 MB",
architecture: "arm64"
}
}
});
Pattern 3: Edge Caching
// Use CloudFront for edge caching
export const headers = () => ({
"Cache-Control": "public, max-age=3600, s-maxage=86400"
});
// Cache at edge for 24 hours
// Browser cache for 1 hour
Cost Monitoring
Pattern 1: Resource Tagging
new sst.aws.Function("Api", {
handler: "src/api.handler",
transform: {
function: {
tags: {
Environment: $app.stage,
Service: "api",
CostCenter: "engineering"
}
}
}
});
Pattern 2: Budget Alerts
// Use AWS Budgets to track costs
// Set up alerts when approaching limits
// Review CloudWatch metrics regularly
Pattern 3: Cost Allocation
// Tag all resources consistently
const tags = {
Project: "my-app",
Environment: $app.stage,
Team: "engineering"
};
// Apply to all resources
new sst.aws.Function("Api", {
transform: {
function: { tags }
}
});
Performance Monitoring
Pattern 1: X-Ray Tracing
new sst.aws.Function("Api", {
handler: "src/api.handler",
transform: {
function: {
tracingConfig: {
mode: "Active" // Enable X-Ray tracing
}
}
}
});
// In code
import { captureAWS } from "aws-xray-sdk-core";
import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
const client = captureAWS(new DynamoDBClient({}));
Pattern 2: CloudWatch Metrics
import { CloudWatchClient, PutMetricDataCommand } from "@aws-sdk/client-cloudwatch";
const cloudwatch = new CloudWatchClient({});
await cloudwatch.send(new PutMetricDataCommand({
Namespace: "MyApp",
MetricData: [{
MetricName: "ProcessingTime",
Value: duration,
Unit: "Milliseconds"
}]
}));
Environment-Specific Optimization
Pattern 1: Development Environment
if ($app.stage === "dev") {
// Smaller, cheaper resources for dev
new sst.aws.Function("Api", {
memory: "256 MB",
timeout: "10 seconds"
});
}
Pattern 2: Production Environment
if ($app.stage === "production") {
new sst.aws.Function("Api", {
memory: "1024 MB", // More resources
timeout: "30 seconds",
transform: {
function: {
reservedConcurrentExecutions: 10,
pointInTimeRecovery: { enabled: true }
}
}
});
}
Best Practices Checklist
Lambda Functions
- Use ARM64 architecture
- Minimize bundle size
- Reuse connections
- Set appropriate memory
- Set appropriate timeout
- Use environment variables for config
DynamoDB
- Use single table design
- Query instead of Scan
- Use batch operations
- Design GSIs carefully
- Enable TTL for expiring data
- Use projection expressions
S3
- Set lifecycle policies
- Use Intelligent Tiering
- Enable CloudFront for static assets
- Use presigned URLs
- Compress files before upload
API Gateway
- Use HTTP API over REST API
- Enable response caching
- Validate requests early
- Use custom domains
Monitoring
- Tag all resources
- Set up CloudWatch alarms
- Enable X-Ray tracing
- Review Cost Explorer monthly
- Set budget alerts
Cost Optimization Strategies
1. Right-Size Resources
Monitor and adjust:
# Check Lambda memory usage
# If max memory used < 60% of allocated, reduce
2. Use Reserved Capacity
For predictable workloads:
- DynamoDB Reserved Capacity
- Lambda Provisioned Concurrency
- Savings Plans
3. Cleanup Unused Resources
# Regular audit
sst remove --stage old-feature
4. Optimize Data Transfer
- Use CloudFront for global distribution
- Keep data in same region
- Use VPC endpoints for AWS services
Common Anti-Patterns
❌ Don't:
- Over-provision memory "just in case"
- Use Scan on large tables
- Keep all data forever
- Ignore CloudWatch metrics
- Deploy to multiple regions unnecessarily
- Use REST API when HTTP API works
✅ Do:
- Start small, scale based on metrics
- Use Query with partition keys
- Set up lifecycle policies
- Monitor and optimize regularly
- Deploy to one region initially
- Use HTTP API by default
Further Reading
- AWS Well-Architected Framework: https://aws.amazon.com/architecture/well-architected/
- AWS Cost Optimization: https://aws.amazon.com/pricing/cost-optimization/
- Lambda Power Tuning: https://github.com/alexcasalboni/aws-lambda-power-tuning
- SST Docs: https://sst.dev/docs
Weekly Installs
8
Repository
tejovanthn/rasikalifeFirst Seen
10 days ago
Security Audits
Installed on
opencode8
gemini-cli8
claude-code8
github-copilot8
codex8
kimi-cli8