Chapter 34: Serverless & FaaS
Serverless computing represents a paradigm shift where infrastructure management is abstracted away. Despite the name, servers still exist—you just don't manage them. This chapter explores Functions as a Service (FaaS), cold starts, event-driven patterns, and when serverless makes sense versus containers or VMs.
1. What is Serverless
Serverless is a cloud execution model where the provider manages all infrastructure—servers, scaling, patching, and capacity planning. You deploy code, and the platform handles everything else.
Core Characteristics
- No Server Management: No provisioning, patching, or capacity planning
- Pay-Per-Invocation: Billed per request and execution time, not for idle capacity
- Auto-Scale to Zero: Scales from 0 to thousands of instances automatically
- Event-Driven: Functions triggered by events (HTTP, queue, storage, scheduler)
- Stateless Execution: Each invocation is independent (ephemeral storage only)
- FaaS: AWS Lambda, Azure Functions, Google Cloud Functions
- Managed Services: DynamoDB, S3, API Gateway, EventBridge
- Containers: AWS Fargate, Google Cloud Run (serverless containers)
- BaaS: Firebase, Auth0, Algolia (backend services)
Serverless vs Traditional
| Aspect | Traditional (VMs/Containers) | Serverless |
|---|---|---|
| Provisioning | Manual (EC2, ECS, k8s) | Automatic |
| Scaling | Configure ASG, HPA | Automatic (0 to thousands) |
| Idle Cost | Pay for running instances | Zero (pay per invocation) |
| Cold Start | None (always running) | Yes (container + runtime init) |
| Execution Duration | Unlimited | 15 minutes max (Lambda) |
| State | Persistent (local disk, memory) | Ephemeral (/tmp cleared) |
2. FaaS (Functions as a Service)
FaaS is the most common serverless model: deploy functions that execute in response to events. The provider manages runtime, scaling, and infrastructure.
Major FaaS Providers
| Provider | Service | Runtimes | Max Timeout | Max Memory |
|---|---|---|---|---|
| AWS | Lambda | Node, Python, Java, Go, .NET, Ruby, Custom | 15 minutes | 10 GB |
| Azure | Functions | Node, Python, Java, .NET, PowerShell | 10 minutes (Consumption), Unlimited (Premium) | 1.5 GB (Consumption), 14 GB (Premium) |
| Google Cloud | Cloud Functions | Node, Python, Go, Java, .NET, Ruby, PHP | 60 minutes (2nd gen) | 16 GB |
| Cloudflare | Workers | JavaScript, WASM | 30 seconds (free), no limit (paid) | 128 MB |
FaaS Execution Model
for ~10-15 minutes
Lambda Anatomy
// Node.js Lambda Handler
export const handler = async (event, context) => {
// event: trigger data (API Gateway request, S3 event, etc.)
// context: runtime info (requestId, functionName, remainingTime)
console.log('Event:', JSON.stringify(event));
console.log('Request ID:', context.requestId);
console.log('Time remaining (ms):', context.getRemainingTimeInMillis());
// Business logic
const result = await processData(event.body);
// Return response
return {
statusCode: 200,
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ result })
};
};
3. Cold Starts
A cold start is the initialization latency when a function is invoked after being idle. This is the biggest drawback of serverless for latency-sensitive applications.
Cold Start Phases
- Container Creation: AWS provisions execution environment (micro-VM or container)
- Runtime Initialization: Load runtime (Node.js, Python, JVM, .NET CLR)
- Code Initialization: Download deployment package, import dependencies, run init code
- Handler Execution: Run your function code (this is NOT part of cold start)
Available?} B -->|Yes| C[Execute Handler
~1-10ms] B -->|No| D[Create Container
~50-200ms] D --> E[Load Runtime
~100-500ms] E --> F[Load Code
~50-300ms] F --> G[Run Init Code
~50-1000ms] G --> C style C fill:#90EE90 style D fill:#FFB6C1 style E fill:#FFB6C1 style F fill:#FFB6C1 style G fill:#FFB6C1
Cold Start Latencies (AWS Lambda)
| Runtime | Cold Start (No Deps) | Cold Start (With Deps) | Warm Start |
|---|---|---|---|
| Node.js | 100-200 ms | 200-500 ms | 1-10 ms |
| Python | 150-300 ms | 300-800 ms | 1-10 ms |
| Go | 100-200 ms | 150-300 ms | 1-5 ms |
| Java (JVM) | 800-1500 ms | 2000-4000 ms | 5-20 ms |
| Java (SnapStart) | 200-500 ms | 300-800 ms | 5-20 ms |
| .NET | 500-1000 ms | 1000-2000 ms | 5-15 ms |
- Provisioned Concurrency: Keep N instances always warm (eliminates cold starts, but costs more)
- Keep-Warm Strategy: Scheduled pings (CloudWatch Events) every 5-10 minutes
- Smaller Packages: Reduce dependencies, use Lambda Layers, tree-shake bundles
- Choose Fast Runtimes: Go/Node.js over Java/.NET (or use SnapStart for Java)
- VPC Optimization: Avoid VPC if possible (adds ~1s cold start); use Hyperplane ENI if required
- AWS SnapStart (Java): Snapshot initialized function, restore from snapshot (10x faster)
- Consider Containers: If cold starts unacceptable, use ECS/Fargate/Cloud Run with min instances
Cold Start Triggers
- First Invocation: Function never run before
- Idle Timeout: No invocations for ~10-15 minutes (container recycled)
- Concurrency Spike: Traffic exceeds warm containers (need new instances)
- Code Deployment: New version deployed (old containers drained)
- Configuration Change: Memory, timeout, environment variables updated
Provisioned Concurrency
Keep N execution environments initialized and warm at all times. Eliminates cold starts but incurs hourly charges (similar to always-on instances).
# Configure provisioned concurrency
aws lambda put-provisioned-concurrency-config \
--function-name my-function \
--provisioned-concurrent-executions 10
# Cost: ~$0.015/hour per GB-hour (vs $0.0000166667/GB-second on-demand)
4. Event-Driven Architecture
Serverless functions are event-driven: they execute in response to triggers from other services. This decouples components and enables reactive, scalable architectures.
AWS Lambda Event Sources
| Event Source | Use Case | Invocation Type | Concurrency Model |
|---|---|---|---|
| API Gateway | REST/HTTP APIs | Synchronous | 1 invocation per request |
| S3 | Object upload/delete | Asynchronous | 1 invocation per object event |
| DynamoDB Streams | Database changes (CDC) | Stream (Poll) | 1 invocation per shard |
| SQS | Queue processing | Stream (Poll) | Batch size 1-10 msgs, concurrent pollers |
| Kinesis | Real-time stream processing | Stream (Poll) | 1 invocation per shard |
| SNS | Pub/sub messaging | Asynchronous | 1 invocation per message |
| EventBridge | Event bus, scheduled tasks | Asynchronous | 1 invocation per event |
| ALB (Load Balancer) | HTTP load balancing | Synchronous | 1 invocation per request |
Event-Driven Pattern Example
- Synchronous: Caller waits for response (API Gateway, ALB). Function errors propagate to caller.
- Asynchronous: Lambda queues invocation, returns immediately (S3, SNS). Retries on failure (2 times by default).
- Stream (Poll-Based): Lambda polls source (SQS, Kinesis, DynamoDB Streams). Batch processing.
Event Structure Examples
// API Gateway Event
{
"httpMethod": "POST",
"path": "/users",
"headers": { "Content-Type": "application/json" },
"body": "{\"name\":\"Alice\"}",
"requestContext": { "requestId": "abc123" }
}
// S3 Event
{
"Records": [{
"eventName": "ObjectCreated:Put",
"s3": {
"bucket": { "name": "my-bucket" },
"object": { "key": "uploads/file.pdf", "size": 1024000 }
}
}]
}
// SQS Event (Batch)
{
"Records": [
{ "messageId": "msg1", "body": "{\"orderId\":123}" },
{ "messageId": "msg2", "body": "{\"orderId\":124}" }
]
}
5. Execution Model
Lambda Lifecycle
- Invocation: Function triggered by event or direct invocation
- Execution Environment: AWS creates/reuses micro-VM with runtime
- Handler Execution: Your code runs (subject to timeout, memory limits)
- Response: Return value sent to caller (synchronous) or discarded (async)
- Container Reuse: Environment kept warm for next invocation (~10-15 min)
Key Constraints
| Constraint | AWS Lambda Limit | Notes |
|---|---|---|
| Timeout | 15 minutes max | Configure 1s to 900s. Use Step Functions for longer workflows. |
| Memory | 128 MB to 10 GB | CPU scales proportionally (1792 MB = 1 vCPU, 10 GB = ~6 vCPUs) |
| Ephemeral Storage | 512 MB to 10 GB (/tmp) | Cleared between invocations (but may persist across warm starts) |
| Deployment Package | 50 MB (zipped), 250 MB (unzipped) | Use Lambda Layers or container images (10 GB) for large deps |
| Concurrency | 1000 concurrent executions (default) | Soft limit, can request increase. Reserved concurrency per function. |
| Environment Variables | 4 KB total | Use Systems Manager Parameter Store / Secrets Manager for large configs |
Lambda allocates CPU proportional to memory:
- 128 MB: ~0.07 vCPU (very slow)
- 1792 MB: 1 full vCPU
- 3584 MB: 2 vCPUs
- 10 GB: ~6 vCPUs
Key insight: If your function is CPU-bound (image processing, data transformation), increasing memory speeds it up AND may reduce cost (faster execution offsets higher per-ms cost).
Stateless Execution
Lambda functions are stateless: each invocation is independent. However, containers may be reused (warm starts), so:
- Global Variables Persist: Across warm invocations (use for connection pooling, caches)
- /tmp Storage Persists: Across warm invocations (but not guaranteed)
- Environment Variables: Set at deployment time, same for all invocations
// Connection pooling example (global scope)
import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
const dynamoDb = new DynamoDBClient({}); // Created once, reused across warm invocations
export const handler = async (event) => {
// dynamoDb client reused (no reconnection overhead)
const result = await dynamoDb.send(new GetItemCommand({ ... }));
return result;
};
6. Common Patterns
1. API Backend
Use Lambda + API Gateway to build serverless REST APIs. Each endpoint is a separate function (or use monolithic handler).
Pros: Zero idle cost, auto-scale, pay per request
Cons: Cold starts (add provisioned concurrency for critical paths), 15-min timeout
2. Event Processing
Process events from queues, streams, or storage. Common for ETL, notifications, and async workflows.
Example: Image upload to S3 triggers Lambda to resize, OCR text, store metadata in DynamoDB.
3. Scheduled Tasks (Cron Jobs)
Use EventBridge (CloudWatch Events) to trigger Lambda on a schedule.
# EventBridge rule: every 5 minutes
rate(5 minutes)
# Cron expression: daily at 2 AM UTC
cron(0 2 * * ? *)
Use Cases: Backups, cleanup, report generation, health checks
4. Stream Processing
Process real-time streams from Kinesis or DynamoDB Streams.
Note: Lambda processes stream in batches (100-10000 records). Parallelism = shard count.
5. Orchestration with Step Functions
Coordinate multiple Lambda functions into workflows. Step Functions handles retries, error handling, and state management.
Use Case: Order processing (validate payment, update inventory, send confirmation, handle failures)
- Step Functions: Orchestrate multiple short Lambdas (workflow can run days/months)
- ECS/Fargate: Use containers for tasks requiring hours (batch processing, ML training)
- Batch: AWS Batch for large-scale compute jobs
7. Limitations
| Limitation | Impact | Mitigation |
|---|---|---|
| Cold Starts | 100-4000ms latency spikes | Provisioned concurrency, keep-warm, faster runtimes (Go/Node), SnapStart |
| Timeout (15 min) | Cannot run long-running tasks | Step Functions (chain functions), ECS/Fargate for long tasks, AWS Batch |
| Stateless | No persistent local storage | Use S3, DynamoDB, EFS (mounted filesystem), ElastiCache |
| Vendor Lock-In | Tight coupling to AWS APIs | Abstract event handling, use containers (Fargate/ECS), multi-cloud frameworks (Serverless Framework) |
| Debugging Difficulty | No SSH, limited observability | CloudWatch Logs, X-Ray tracing, Lambda Insights, local testing (SAM CLI, LocalStack) |
| Cost at High Scale | Per-request pricing expensive at 24/7 high traffic | Use containers (ECS/Fargate) or VMs for sustained high throughput |
| Concurrency Limits | 1000 concurrent executions (default) | Request limit increase, reserved concurrency per function, throttling via SQS |
| Package Size | 50 MB zipped, 250 MB unzipped | Lambda Layers, container images (10 GB), dependency optimization |
| VPC Cold Starts | +1-2 seconds for VPC ENI attachment | Hyperplane ENI (newer, faster), avoid VPC if public access acceptable |
- Latency-Critical APIs: Sub-50ms P99 required, cannot tolerate cold starts
- Long-Running Tasks: Video encoding (hours), ML training, batch jobs (use Batch, ECS)
- Sustained High Traffic: 24/7 high QPS (containers cheaper than per-request pricing)
- Stateful Applications: WebSocket servers, game servers, caching layers (use EC2, ECS)
- Complex Networking: Requires specific IP addresses, VPC peering, firewall rules
- Legacy Frameworks: Large monoliths difficult to refactor (use containers as intermediate step)
8. BaaS (Backend as a Service)
BaaS provides fully managed backend services: authentication, databases, storage, search, and APIs. You integrate via SDKs; provider manages infrastructure, scaling, and security.
BaaS vs FaaS
| Aspect | FaaS (Lambda) | BaaS (Firebase, Supabase) |
|---|---|---|
| Control | Write custom code | Use pre-built services |
| Flexibility | Any logic, any language | Limited to provider's features |
| Setup Complexity | Configure triggers, IAM, API Gateway | SDK integration, minimal config |
| Use Case | Custom business logic, integrations | Common backend features (auth, CRUD) |
Popular BaaS Providers
| Service | Features | Best For |
|---|---|---|
| Firebase | Realtime Database, Auth, Cloud Functions, Hosting, Analytics | Mobile apps, realtime features (chat, collaboration) |
| Supabase | Postgres, Auth, Storage, Realtime, Edge Functions | Open-source Firebase alternative, SQL-first apps |
| Auth0 | Authentication, SSO, MFA, User Management | Enterprise auth, social login, identity management |
| Algolia | Search API, instant results, typo tolerance | Product search, site search, autocomplete |
| Contentful | Headless CMS, content API | Content-driven sites, marketing pages |
| Stripe | Payment processing, subscriptions, invoicing | E-commerce, SaaS billing |
BaaS Example (Firebase)
// Frontend code (no backend required)
import { initializeApp } from 'firebase/app';
import { getAuth, signInWithEmailAndPassword } from 'firebase/auth';
import { getFirestore, collection, addDoc } from 'firestore/firestore';
const app = initializeApp({ /* config */ });
const auth = getAuth(app);
const db = getFirestore(app);
// Authenticate
await signInWithEmailAndPassword(auth, email, password);
// Write to database
await addDoc(collection(db, 'orders'), {
userId: auth.currentUser.uid,
items: cartItems,
total: 99.99,
createdAt: new Date()
});
// Realtime listener (updates UI automatically)
onSnapshot(collection(db, 'orders'), (snapshot) => {
snapshot.forEach(doc => console.log(doc.data()));
});
- Rapid Prototyping: MVP, hackathons, startups (validate idea quickly)
- Standard Features: Auth, CRUD, search (don't reinvent the wheel)
- Small Teams: Limited backend expertise or resources
- Complex Logic: Custom workflows, multi-step transactions
- Data Ownership: Regulatory requirements, data residency
- Vendor Lock-In Risk: Strategic apps, long-term (10+ years)
9. Serverless vs Containers
Decision Matrix
| Factor | Choose Serverless (Lambda) | Choose Containers (ECS/Fargate/k8s) |
|---|---|---|
| Traffic Pattern | Sporadic, unpredictable, bursty (0 to 1000 QPS) | Sustained high traffic (24/7, stable QPS) |
| Latency Requirement | Can tolerate 100-500ms cold starts (or use provisioned concurrency) | P99 latency under 50ms, no cold starts acceptable |
| Execution Duration | Under 15 minutes | Hours or indefinite (batch jobs, streaming) |
| State | Stateless, event-driven | Stateful (WebSockets, sessions, caches) |
| Cost Profile | Low traffic (idle cost = 0) | High sustained traffic (per-request pricing expensive) |
| Complexity | Simple functions, microservices | Monoliths, complex apps, legacy systems |
| Deployment | Frequent small updates | Infrequent deployments, long-running processes |
| Vendor Lock-In | Acceptable (or use Serverless Framework for portability) | Must avoid (use containers for multi-cloud portability) |
Analysis:
- Execution Duration: Video transcoding can take hours (Lambda max = 15 min). Containers win.
- Cost: Sporadic traffic (users upload videos irregularly). Serverless could scale to zero. Serverless wins.
- State: Stateless (read video from S3, transcode, write back). Serverless OK.
Solution: Use AWS Batch or ECS Fargate (serverless containers):
- ECS Fargate: Run containers without managing EC2 (scales to zero with Application Auto Scaling)
- AWS Batch: Optimized for batch jobs (queues, priorities, spot instances)
- Trigger: Lambda receives S3 upload event, submits Fargate/Batch job for transcoding
Hybrid Approach
Many systems use both:
- Lambda: API Gateway endpoints, event processing, short tasks
- ECS/Fargate: Long-running services, stateful apps, background workers
- Example: Lambda handles API requests, publishes to SQS; Fargate workers process queue (video transcoding, ETL)
10. Cost Analysis
Lambda Pricing Model (AWS, us-east-1)
- Requests: $0.20 per 1M requests
- Compute (GB-seconds): $0.0000166667 per GB-second
- Provisioned Concurrency: $0.0000041667 per GB-second (~$0.015/hour per GB)
- Free Tier: 1M requests + 400,000 GB-seconds per month (permanent)
Cost Example: REST API
Scenario: 1 million requests/month, 1 GB memory, 200ms avg execution
# Lambda (on-demand)
Requests: 1M * $0.20 / 1M = $0.20
Compute: 1M * 0.2s * 1GB * $0.0000166667 = $3.33
Total: $3.53/month
# EC2 t3.small (always-on)
Instance cost: $0.0208/hour * 730 hours = $15.18/month
# Break-even: ~4M requests/month (at 200ms, 1GB)
- Low/Sporadic Traffic: Under 5M requests/month (scales to zero)
- Short Execution: Under 1 second (minimize compute cost)
- High Sustained Traffic: 24/7, millions of requests/day
- Long Execution: Minutes per request (compute cost accumulates)
Cost Comparison Table
| Traffic Pattern | Lambda Cost (1GB, 200ms) | EC2 t3.small Cost | Winner |
|---|---|---|---|
| 100K requests/month | $0.35 | $15.18 | Lambda |
| 1M requests/month | $3.53 | $15.18 | Lambda |
| 10M requests/month | $35.30 | $15.18 | EC2 |
| 100M requests/month | $353.00 | $15.18 (or $50 for m5.large) | EC2 |
Provisioned Concurrency Cost
# Keep 10 instances warm (1 GB each, 24/7)
Provisioned cost: 10 * 1GB * 730 hours * $0.015 = $109.50/month
# On-demand compute still charged for execution time
Execution cost: (same as on-demand)
# Total: $109.50 + execution cost
When to use: Latency-critical APIs with unpredictable traffic (cold starts unacceptable, but don't want always-on VMs). Often cheaper than EC2 if traffic is still bursty.
Cost Optimization Strategies
- Right-Size Memory: Test different memory settings (CPU scales with memory). Higher memory may be cheaper if faster execution offsets cost.
- Batch Processing: Process SQS/Kinesis in batches (10-100 records) to amortize invocation cost.
- Connection Pooling: Reuse DB connections across warm invocations (avoid reconnection overhead).
- Reduce Package Size: Smaller deployments = faster cold starts = lower init cost.
- Use Lambda Layers: Share dependencies across functions (reduce redundant uploads, faster deploys).
- Monitor with Cost Explorer: Track Lambda cost by function, identify expensive outliers.
- Shift to Containers: At high sustained traffic (10M+ req/month), ECS/Fargate often cheaper.
- Check CloudWatch Metrics: Invocation count, duration, errors (unexpected traffic spike? infinite loop?)
- Review Cost Explorer: Break down by function, region, time (which function is expensive?)
- Check Logs: CloudWatch Logs for errors, retries (async invocations retry 2x on failure)
- Look for Infinite Loops: Lambda invoking itself via S3 events (misconfigured triggers)
- Memory Configuration: Over-provisioned memory (1GB when 128MB sufficient)
- DLQ/SQS Backlog: Failed messages retrying infinitely (configure DLQ, set max retries)
Chapter Summary
- Serverless = Operational Abstraction: No server management, pay per use, auto-scale to zero. Includes FaaS, managed services, and serverless containers.
- FaaS (Lambda, Azure Functions, Cloud Functions): Deploy code as functions, triggered by events. Max 15-min timeout (Lambda), stateless execution.
- Cold Starts: Initialization latency (100-4000ms) when function idle. Mitigate with provisioned concurrency, keep-warm, fast runtimes (Go/Node), SnapStart (Java).
- Event-Driven Architecture: Functions triggered by API Gateway, S3, SQS, DynamoDB Streams, EventBridge. Synchronous, asynchronous, or stream (poll-based) invocations.
- Execution Model: 15-min max timeout, 128MB-10GB memory (CPU scales proportionally), /tmp ephemeral storage, 1000 concurrent executions (default).
- Common Patterns: API backend (API Gateway + Lambda), event processing (S3/SQS triggers), scheduled tasks (EventBridge), stream processing (Kinesis/DynamoDB Streams), orchestration (Step Functions).
- Limitations: Cold starts, 15-min timeout, stateless, vendor lock-in, debugging difficulty, expensive at high sustained scale.
- BaaS vs FaaS: BaaS (Firebase, Supabase, Auth0) provides pre-built services (auth, DB, search). FaaS (Lambda) is custom code. Use BaaS for rapid prototyping, standard features.
- Serverless vs Containers: Serverless for sporadic traffic, short tasks, stateless apps. Containers for sustained high traffic, long-running tasks, stateful apps, low latency (no cold starts).
- Cost: Lambda cheaper for low/sporadic traffic (under 5M req/month). Containers cheaper for sustained high traffic (10M+ req/month). Break-even depends on execution duration and memory.
Interview Tips
- Define Serverless Broadly: Not just Lambda—includes managed services (DynamoDB, S3), serverless containers (Fargate, Cloud Run), BaaS (Firebase).
- Cold Start Trade-Offs: Acknowledge the problem, propose solutions (provisioned concurrency, faster runtimes, keep-warm), and know when to switch to containers.
- Cost Awareness: Serverless is NOT always cheaper. At high sustained traffic, containers/VMs are more economical. Understand break-even points.
- Event-Driven Design: Explain synchronous (API Gateway), asynchronous (S3, SNS), and stream-based (Kinesis, SQS) invocation models.
- Limitations: Be honest about constraints (15-min timeout, stateless, cold starts). Propose architectures that work around them (Step Functions, ECS/Fargate for long tasks).
- Hybrid Architectures: Most production systems use both serverless and containers. Lambda for APIs/events, ECS/Fargate for long-running services.
- Use Cases: Serverless shines for bursty workloads (webhooks, image processing, scheduled jobs). Not ideal for latency-critical APIs (under 50ms P99) or stateful apps (WebSockets).