What are the architectural patterns for serverless vs container-based backends?
Quick Answer
Serverless (Lambda + API Gateway) excels for event-driven, spiky workloads with sub-100ms startup requirements and per-request billing, while container-based (ECS Fargate or EKS) suits long-running processes, steady-state traffic, WebSocket connections, and workloads needing more than 10GB memory or 15-minute execution windows. The decision hinges on request duration, concurrency patterns, cold start tolerance, state management needs, and unit economics at your traffic volume.
Detailed Answer
Choosing between serverless and containers is not a binary decision; it is a spectrum, and most production systems use both. Think of serverless like a taxi: you pay per trip, there is no vehicle to maintain, but you cannot customize the car and you wait for one to arrive (cold start). Containers are like a company car: fixed monthly cost whether you drive it or not, but it is always ready and you can customize everything under the hood.
Serverless Architecture (Lambda + API Gateway + DynamoDB). API Gateway receives HTTP requests and routes them to Lambda functions. Each Lambda invocation is isolated, runs for up to 15 minutes, and scales to 1000 concurrent executions per region by default (soft limit, requestable to tens of thousands). Lambda charges per 1ms of execution time and per request ($0.20 per million requests, $0.0000166667 per GB-second). For a workload handling 10 million requests/month at 200ms average duration with 512MB memory, the Lambda cost is approximately $18.67/month. This is transformatively cheap for startups.
However, serverless has hard constraints. Cold starts add 100ms-3s depending on runtime (Java/C# are worst, Node.js/Python best, and Rust/Go with custom runtime are near-zero). Provisioned Concurrency eliminates cold starts but adds fixed cost, defeating the pay-per-request model. Lambda has a 10GB memory ceiling, 10GB ephemeral storage, and cannot maintain persistent connections (WebSockets require API Gateway WebSocket API with connection management in DynamoDB). Each Lambda invocation is stateless, so session state must live in ElastiCache or DynamoDB. Lambda functions behind API Gateway have a 29-second timeout (API Gateway limit), even though Lambda itself allows 15 minutes.
Container Architecture (ECS Fargate or EKS behind ALB). An Application Load Balancer distributes requests to Fargate tasks running your Docker containers. Containers maintain state in memory across requests, support WebSocket connections natively, and have no cold start once running. ECS Fargate charges per vCPU-hour ($0.04048) and per GB-hour ($0.004445). A service running 2 tasks with 1 vCPU and 2GB RAM 24/7 costs approximately $96.15/month. At low traffic, this is more expensive than serverless. At high traffic (millions of requests/day), the unit economics flip because you are paying for capacity, not per-request.
EKS adds Kubernetes orchestration: service mesh (Istio/Linkerd), horizontal pod autoscaling based on custom metrics, rolling deployments with canary analysis, and a vast ecosystem of operators. However, EKS adds $73/month for the control plane, plus worker node costs, and requires Kubernetes expertise. Use EKS when you need multi-cloud portability, have a platform team, or run 20+ microservices that benefit from the Kubernetes ecosystem.
Hybrid patterns are the real-world answer. Use Lambda for event-driven glue: processing S3 uploads, responding to DynamoDB Streams, handling SNS notifications, and running scheduled cron jobs. Use containers for your core API that handles synchronous HTTP traffic with predictable latency requirements. Use Lambda@Edge or CloudFront Functions for edge logic (A/B testing, geolocation routing, header manipulation). The API Gateway can route /api/* to an ALB (containers) and /webhooks/* to Lambda in the same application.
State management differs fundamentally. Containers can use in-memory caching (local LRU cache) for hot data, reducing ElastiCache calls. Lambda functions must externalize all state, which adds latency and cost for every cache lookup. For machine learning inference, containers allow loading large models into memory once and serving many requests, while Lambda must load the model on every cold start (or use Provisioned Concurrency with large memory).
Production gotchas: Lambda concurrent execution limits are per-region, not per-function, so one runaway function can starve all other functions in the account. Use reserved concurrency to isolate critical functions. ECS Fargate task startup time is 30-60 seconds for pulling images; use ECR with VPC endpoints and slim base images (Alpine, distroless) to minimize pull time. ALB health checks default to 30-second intervals; reduce to 10 seconds with a 2-unhealthy-threshold to speed up failed task replacement. Never run ECS tasks with the awsvpc network mode in a subnet without enough available IP addresses; each task consumes one ENI (and one IP).