skills/tejovanthn/rasikalife/aws-optimization-sst

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

Weekly Installs
8
First Seen
10 days ago
Installed on
opencode8
gemini-cli8
claude-code8
github-copilot8
codex8
kimi-cli8