DEV Community

AWS Serverless Patterns and Anti-Patterns: What Works, What Breaks, and When to Use What

Serverless on AWS isn't "just use Lambda." It's a design philosophy: let AWS manage the infrastructure, pay only for what you use, and build with managed services that scale independently. But the patterns that work in serverless are fundamentally different from traditional architectures - and the anti-patterns are expensive to learn the hard way. This guide covers the patterns that work in production, the anti-patterns that waste money or cause outages, and the decision framework for when serverless is the right (or wrong) choice. The Serverless Building Blocks โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ AWS SERVERLESS STACK โ”‚ โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค โ”‚ COMPUTE โ”‚ Lambda | Fargate (serverless containers) โ”‚ โ”‚ API โ”‚ API Gateway (REST/HTTP/WebSocket) | AppSync (GraphQL)โ”‚ โ”‚ ORCHESTRATION โ”‚ Step Functions | EventBridge Scheduler โ”‚ โ”‚ MESSAGING โ”‚ SQS | SNS | EventBridge โ”‚ โ”‚ STORAGE โ”‚ S3 | DynamoDB | Aurora Serverless โ”‚ โ”‚ STREAMING โ”‚ Kinesis | DynamoDB Streams | MSK Serverless โ”‚ โ”‚ AUTH โ”‚ Cognito | IAM | Lambda Authorizers โ”‚ โ”‚ OBSERVABILITY โ”‚ CloudWatch | X-Ray | Application Signals โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ Key principle: In serverless, you compose applications from managed services. Lambda is the glue between them - not the application itself. Pattern 1: Synchronous API (Request/Response) The most common serverless pattern: HTTP API backed by Lambda. Client โ†’ API Gateway โ†’ Lambda โ†’ DynamoDB / Aurora Serverless โ”‚ Response โ† โ”€ โ”€ โ”€ โ”€ โ”€ โ”€ โ”˜ Best Practices - API Gateway HTTP API (not REST API) - cheaper, faster, simpler for most cases - One Lambda per route (single responsibility) - not a monolith Lambda - Keep Lambda warm - use Provisioned Concurrency for latency-sensitive endpoints - DynamoDB for simple access patterns - scales with traffic, no connection pooling - Aurora Serverless v2 for complex queries - but use RDS Proxy to manage connections When to Choose HTTP API vs REST API | Feature | HTTP API | REST API | |---|---|---| | Cost | $1.00/million requests | $3.50/million requests | | Latency | Lower (~10ms added) | Higher (~30ms added) | | Features | JWT auth, CORS, Lambda integration | WAF, usage plans, API keys, caching, request validation | | Choose when | Standard APIs, cost-sensitive | Need WAF, throttling plans, request transforms | Pattern 2: Async Event Processing Events trigger Lambda. Processing happens independently of the caller. S3 Upload โ”€โ”€โ†’ Lambda: process image โ”€โ”€โ†’ S3: store thumbnail SQS Message โ”€โ”€โ†’ Lambda: process order โ”€โ”€โ†’ DynamoDB: update status EventBridge โ”€โ”€โ†’ Lambda: handle event โ”€โ”€โ†’ SNS: send notification Best Practices - Always use Dead Letter Queues (DLQ) - failed events go to DLQ, not lost - Design for idempotency - events may be delivered more than once - Batch processing - SQS Lambda trigger processes up to 10 messages per invocation (cost efficient) - Set reserved concurrency - prevent one function from consuming all account concurrency - Use event filtering - Lambda event source filtering reduces invocations (cheaper + simpler) Event Source Filtering Example { "FilterCriteria": { "Filters": [ { "Pattern": "{"body": {"status": ["critical"]}}" } ] } } Lambda only invokes for messages where body.status == "critical" . Other messages are filtered out at the service level (free). Pattern 3: Workflow Orchestration (Step Functions) For multi-step processes with branching, retries, and error handling. Step Function: โ”œโ”€โ”€ Validate input โ”œโ”€โ”€ Process payment (Lambda) โ”‚ โ”œโ”€โ”€ Success โ†’ Reserve inventory (Lambda) โ”‚ โ””โ”€โ”€ Failure โ†’ Notify customer (SNS) โ†’ End โ”œโ”€โ”€ Ship order (Lambda) โ”œโ”€โ”€ Wait 7 days โ””โ”€โ”€ Send follow-up email (Lambda) Step Functions: Express vs Standard | Feature | Standard | Express | |---|---|---| | Duration | Up to 1 year | Up to 5 minutes | | Pricing | Per state transition ($0.025/1000) | Per execution + duration | | Execution model | Exactly-once | At-least-once | | History | Full execution history (90 days) | CloudWatch Logs only | | Use case | Long-running workflows, human approval | High-volume, short processing (ETL, transforms) | Direct Service Integrations (Skip Lambda) Step Functions can call 200+ AWS services directly without Lambda: { "Type": "Task", "Resource": "arn:aws:states:::dynamodb:putItem", "Parameters": { "TableName": "Orders", "Item": { "orderId": {"S.$": "$.orderId"}, "status": {"S": "confirmed"} } } } No Lambda needed - Step Functions writes to DynamoDB directly. Cheaper, fewer moving parts, lower latency. Rule: If your Lambda only calls one AWS API - replace it with a direct integration. Pattern 4: Streaming / Real-Time Processing For continuous data ingestion and processing. IoT Devices โ”€โ”€โ†’ Kinesis โ”€โ”€โ†’ Lambda (real-time) โ”€โ”€โ†’ DynamoDB โ”‚ โ””โ”€โ”€โ†’ Firehose โ”€โ”€โ†’ S3 (data lake) Best Practices - Kinesis for ordering and replay - Lambda for real-time processing - Firehose for batched delivery - no code needed for S3/Redshift/OpenSearch - Tumbling windows - Lambda aggregates over time windows natively - Bisect on error - Kinesis + Lambda can split failed batches to isolate the bad record Pattern 5: Fan-Out / Scatter-Gather One trigger spawns many parallel processes, results are aggregated. API โ†’ Step Function (Distributed Map): โ”œโ”€โ”€ Process item 1 (Lambda) โ”œโ”€โ”€ Process item 2 (Lambda) โ”œโ”€โ”€ Process item 3 (Lambda) โ””โ”€โ”€ ... (10,000 concurrent) โ†’ Aggregate results โ†’ Response Step Functions Distributed Map processes millions of items with up to 10,000 concurrent executions. Use for: - Batch processing large datasets from S3 - Parallel API calls to external services - Large-scale data transformation Pattern 6: GraphQL API (AppSync) For applications needing flexible, client-driven queries. Client โ†’ AppSync โ†’ Resolvers: โ”œโ”€โ”€ DynamoDB (direct resolver, no Lambda) โ”œโ”€โ”€ Lambda (complex logic) โ”œโ”€โ”€ Aurora (SQL queries) โ””โ”€โ”€ HTTP (external APIs) AppSync advantages over API Gateway + Lambda: - Client fetches exactly what it needs (no over-fetching) - Real-time subscriptions (WebSocket) built in - Direct DynamoDB/Aurora resolvers (no Lambda needed for CRUD) - Caching built in Pattern 7: Scheduled Tasks Replace cron servers with serverless scheduling. EventBridge Scheduler โ†’ Lambda: run cleanup EventBridge Rule (rate/cron) โ†’ Lambda: generate report Step Functions Wait โ†’ Lambda: send reminder EventBridge Scheduler vs EventBridge Rules | Feature | Scheduler | Rules | |---|---|---| | One-time events | โœ… (at specific time) | โŒ | | Timezone support | โœ… (handles DST) | โŒ (UTC only) | | Scale | Millions of schedules | Limited rules per bus | | Use case | Per-entity schedules (user reminders) | System-wide recurring jobs | Anti-Patterns: What NOT to Do Anti-Pattern 1: Lambda Monolith The mistake: Putting your entire Express/Flask app inside one Lambda function. โŒ BAD: Single Lambda handles ALL routes /users, /orders, /products, /admin โ†’ one 50MB Lambda โœ… GOOD: One Lambda per route (or per domain) /users โ†’ users-handler /orders โ†’ orders-handler Why it fails: Cold starts scale with package size. One change requires redeploying everything. No independent scaling per endpoint. Anti-Pattern 2: Lambda Calling Lambda (Synchronous Chain) The mistake: Lambda A calls Lambda B which calls Lambda C, all synchronously. โŒ BAD: Lambda A โ†’ invoke โ†’ Lambda B โ†’ invoke โ†’ Lambda C (paying for A's time while waiting for B and C) โœ… GOOD: Step Functions: A โ†’ B โ†’ C (orchestrated, not nested) Or: A โ†’ SQS โ†’ B โ†’ SQS โ†’ C (async, decoupled) Why it fails: You pay for idle time while waiting. Retry logic becomes complex. Timeouts cascade. Use Step Functions or async messaging instead. Anti-Pattern 3: Recursive Lambda The mistake: Lambda invokes itself (or triggers a loop). โŒ DANGEROUS: Lambda โ†’ writes to S3 โ†’ triggers same Lambda โ†’ writes to S3 โ†’ ... (infinite loop = infinite bill) Fix: Use separate buckets for input/output, or use event source filtering to exclude your own writes. Anti-Pattern 4: VPC Lambda Without Need The mistake: Putting Lambda in a VPC "for security" when it doesn't access VPC resources. Why it fails: VPC Lambda has cold start overhead (ENI creation). If Lambda only calls DynamoDB, S3, or external APIs - it doesn't need VPC. Use VPC only when accessing RDS, ElastiCache, or private EC2 services. Anti-Pattern 5: Over-Orchestration The mistake: Using Step Functions for a simple sequential call that could be a direct Lambda + SDK call. โŒ OVER-ENGINEERED: Step Function โ†’ Lambda (validate) โ†’ Lambda (save to DDB) (3 resources for what one Lambda could do) โœ… APPROPRIATE: Lambda: validate + save to DDB (if it's simple sequential logic) Rule: Use Step Functions when you need branching, retries, parallel execution, wait states, or error handling across multiple services. Don't use it for simple Aโ†’B flows. Anti-Pattern 6: Ignoring Cold Starts in Latency-Sensitive Paths The mistake: Using Lambda for a user-facing API with p99 latency SLA of 15 minutes | Lambda timeout limit | ECS tasks / Step Functions | | Persistent connections (WebSocket server, gRPC stream) | Lambda is request/response | Fargate / EC2 | | Cold start unacceptable ( 250MB) | Lambda size limits | Containers on Fargate | Cost Optimization Patterns | Pattern | Savings | |---|---| | ARM (Graviton) Lambda | 20% cheaper, often 10-30% faster | | Increase memory (reduce duration) | Often cheaper: 256MB ร— 400ms costs same as 512MB ร— 180ms | | Batch SQS messages (10 per invocation) | 10x fewer invocations | | Direct integrations (skip Lambda) | No Lambda cost for simple pass-through | | Event filtering | Reduce unnecessary invocations | | Provisioned Concurrency (only for latency needs) | โš ๏ธ Adds cost - use only where needed | Summary Serverless on AWS works when you follow these principles: - Compose from managed services - Lambda is glue, not the application - Single responsibility - one function per task, not monolith Lambdas - Async by default - use SQS/EventBridge between services, not synchronous chains - Step Functions for orchestrat

Read on DEV Community ↗ ← Back to News

Comments

No comments yet. Start the discussion.