Serverless Architecture
An execution model where cloud providers dynamically manage infrastructure allocation, billing per invocation rather than per idle hour, eliminating server provisioning and management entirely for the application developer.
What it is
Serverless architecture — more precisely, Function-as-a-Service (FaaS) at the compute layer — allows developers to deploy individual functions that are invoked in response to events. The cloud provider instantiates a runtime container to execute the function, bills for the execution duration in millisecond increments, and destroys or re-uses the container without developer involvement. AWS Lambda, Google Cloud Functions, and Azure Functions are the dominant FaaS platforms. Each imposes constraints: Lambda supports Node.js, Python, Java, Go, .NET, Ruby, and custom runtimes; maximum execution time is 15 minutes; memory is configurable from 128 MB to 10 GB; and CPU allocation scales proportionally with memory.
Serverless extends beyond compute to include serverless databases. Amazon DynamoDB and Google Firestore offer auto-scaling capacity with per-read/write billing. Aurora Serverless v2 provides a MySQL/PostgreSQL-compatible database that scales ACUs (Aurora Capacity Units) instantly in response to workload, including to near-zero for infrequently used databases. Serverless message queues (SQS), object storage (S3), and API gateways complete the picture — a fully serverless application has no provisioned infrastructure; every component bills on consumption. This enables the "scale to zero" property: when no traffic flows, you pay nothing.
Why it exists
Traditional compute pricing charges for capacity reserved, not capacity used. A service that handles 10 req/s at noon and 0 req/s at 3 AM still costs the same hourly rate for both periods. For intermittent workloads — event processors, scheduled jobs, webhooks, background image resizing — the idle-time cost often exceeds the execution cost. FaaS eliminates this waste entirely. Additionally, the operational model is fundamentally simpler: no AMI updates, no OS patching, no capacity planning, no cluster sizing decisions. The developer delivers a function and the platform handles everything else.
Event-driven architectures are a natural fit: S3 uploads triggering image processing, SQS messages triggering order fulfillment, EventBridge rules triggering data pipeline steps, and API Gateway requests triggering HTTP handlers. Each event triggers exactly one function execution (or a parallel fan-out), the function completes, and billing stops. Lambda's concurrency model scales to thousands of parallel executions within seconds to handle traffic bursts that would overwhelm traditionally provisioned services — the limit (account default 1,000 concurrent executions) can be raised on request.
When to use
- Event-driven processing: image/video transcoding on upload, document indexing on S3 PUT, order processing on SQS, webhook receivers.
- Scheduled batch jobs: nightly data exports, report generation, database cleanup tasks — the scheduled Lambda model replaces cron jobs running on idle EC2 instances.
- API backends with spiky, unpredictable traffic where paying for idle capacity is wasteful and autoscaling lag is unacceptable.
- Prototype and MVP development where operational simplicity and zero-setup deployment speed are valued over full control.
- Glue code and orchestration: data pipeline steps, cross-service integration, custom CloudWatch alarm responses.
When not to use
- Long-running computations: Lambda's 15-minute maximum execution time and cold start overhead make it unsuitable for multi-hour ML training, large ETL jobs, or any process that would benefit from persistent process state.
- High-frequency, sustained workloads: At constant high throughput, FaaS per-invocation pricing can exceed equivalent provisioned container costs; break-even is typically around 30–50% utilisation for most Lambda workloads.
- Stateful workflows: FaaS functions are stateless; any workflow requiring shared in-memory state, connection pooling, or persistent TCP connections must externalise state — this adds latency and cost for connection-heavy workloads (Lambda connecting to RDS for every invocation).
- Sub-millisecond latency requirements: Cold start latency (100 ms to 3+ seconds depending on runtime and package size) and invocation overhead make Lambda unsuitable for ultra-low-latency responses; a pre-warmed container or co-located process is necessary.
Typical architecture
Serverless API Architecture
─────────────────────────────────────────────────────
Client → API Gateway (REST/HTTP API)
│
┌──────────┼──────────────┐
▼ ▼ ▼
Lambda Lambda Lambda
(GET /user) (POST /order) (DELETE /item)
│ │ │
▼ ▼ ▼
DynamoDB DynamoDB DynamoDB
(users) (orders) (items)
Serverless Event Pipeline
─────────────────────────────────────────────────────
S3 Bucket
[object PUT]
│ S3 Event Notification
▼
Lambda (resize + compress image)
│ put output
▼
S3 Processed Bucket
│ SQS notification
▼
Lambda (update CDN cache, write metadata to DynamoDB)
Cold Start Mitigation
─────────────────────────────────────────────────────
Provisioned Concurrency: N pre-warmed Lambda instances
AWS Lambda SnapStart (Java): pre-initialized snapshots
Minimise package size: tree-shake dependencies
Choose lightweight runtimes: Python/Node over Java
Pros and cons
Pros
- Zero idle cost — billing only during function execution; scale-to-zero is native, not configured.
- Automatic scaling to thousands of concurrent executions without capacity planning or configuration.
- No server management: no OS patching, no runtime upgrades, no cluster sizing.
- Extremely fast deployment iterations — deploying a new Lambda version takes seconds via the AWS CLI or CDK.
- Native integration with AWS event sources (S3, SQS, Kinesis, EventBridge, API Gateway) with zero configuration of event polling.
Cons
- Cold start latency (JVM runtimes: 500 ms–3 s; Python/Node: 50–200 ms) can degrade user-facing API performance without provisioned concurrency.
- Vendor lock-in — Lambda function code and configuration (layers, event source mappings) is tightly coupled to AWS primitives.
- Debugging and observability is harder: no persistent process to attach a debugger to; distributed tracing (X-Ray) adds overhead.
- Database connection exhaustion: Lambda scaling to 1,000+ concurrent executions overwhelms RDS max_connections (typically 100–500); requires RDS Proxy for connection pooling.
- Execution time limit (15 min) and package size limit (250 MB unzipped) exclude certain workloads.
Implementation notes
Structure Lambda functions to maximise the value of the warm execution environment: perform expensive initialisation (database connection via RDS Proxy, SDK client construction, config loading) outside the handler function in the module-level initialization code. This code runs once per container initialisation and is reused across subsequent invocations on the same warm instance. For Java runtimes, use Lambda SnapStart (available for Java 21 managed runtime): the JVM is snapshotted after initialisation and restored for each invocation, reducing cold start from 2–5 seconds to under 200 ms. For Python/Node runtimes, minimise deployment package size using tree-shaking (esbuild for Node.js, pip install --target with selective packages) — smaller packages initialise faster.
For Lambda connecting to RDS, always use RDS Proxy as the endpoint. RDS Proxy maintains a connection pool and multiplexes thousands of Lambda connections onto a smaller pool of actual database connections, preventing connection exhaustion under scale-out. Configure FORCE_IAM_DATABASE_AUTHENTICATION=true for RDS Proxy to use IAM token authentication instead of embedding database passwords. For Lambda concurrency controls: set a ReservedConcurrency on each function to prevent one high-traffic function from exhausting the account concurrency limit and starving other functions; set it lower than the account limit to preserve headroom. Use Provisioned Concurrency (not Reserved Concurrency) for latency-sensitive user-facing APIs that cannot tolerate cold starts.
Common failure modes
- RDS connection pool exhaustion: Lambda scaling to hundreds of concurrent invocations each opening a new database connection exceeds max_connections; always use RDS Proxy or implement connection pooling at the Lambda layer.
- Timeout cascade: Lambda default timeout is 3 seconds; a downstream dependency (database, external API) that is slow causes Lambda timeouts and retries, amplifying the load on the downstream — set timeout at 90th percentile latency and implement circuit breakers.
- SQS visibility timeout misconfiguration: Lambda processing time exceeds the SQS visibility timeout, causing messages to reappear in the queue and be processed multiple times; set visibility timeout to at least 6× Lambda timeout.
- Cold start causing p99 latency spikes: Ignoring cold start for latency-sensitive endpoints; enable Provisioned Concurrency for functions on the critical user path and schedule scaling actions before predictable traffic increases.
- Lambda invocation payload limit: Lambda has a 6 MB synchronous and 256 KB asynchronous invocation payload limit; large event payloads (e.g., S3 batch export) must use the Claim Check pattern — pass an S3 reference, not the payload inline.
Decision checklist
- Is the workload event-driven or intermittent enough that pay-per-invocation billing will be cheaper than provisioned containers at your expected invocation volume?
- Does the function complete within 15 minutes? (If not, step functions or ECS tasks are required.)
- If the function connects to a relational database, is RDS Proxy configured to prevent connection exhaustion?
- For user-facing APIs, have you evaluated cold start latency impact and planned Provisioned Concurrency or SnapStart accordingly?
- Is distributed tracing (X-Ray or OpenTelemetry Lambda layer) enabled to diagnose latency issues across function chains?
- Are dead-letter queues configured for asynchronous Lambda invocations to capture failed executions for replay?
Example use cases
- Image processing pipeline: Users upload profile photos to S3; S3 event triggers Lambda (Python + Pillow) that resizes to 3 dimensions and writes to processed/ prefix; total infrastructure cost for 500K uploads/month is ~$2 vs ~$50/month for an always-on ECS task.
- Webhook receiver: Third-party payment provider sends payment events via webhook; API Gateway + Lambda validates HMAC signature, writes to SQS, returns 200 immediately; downstream Lambda processes SQS messages; entire pipeline is serverless and scales from 10 to 10,000 events/minute without configuration.
- Nightly report generation: EventBridge scheduled rule triggers Lambda at 02:00 UTC; Lambda queries Aurora Serverless (which scales up from near-zero ACUs), generates PDF reports, uploads to S3, sends SES emails; Aurora Serverless scales back to near-zero after the job; monthly cost for the entire pipeline: ~$15.
Related patterns
- Cloud-Native Design Principles — serverless is the most extreme expression of stateless, disposable, managed-service-first design.
- Auto-Scaling Patterns — KEDA provides serverless-like scaling for container workloads that cannot use FaaS.
- Event-Driven Architecture — FaaS functions are the natural compute unit for event-driven architectures.
- Serverless Architecture Style — broader coverage of the serverless architectural style beyond FaaS.