Lambda — When & How (Serverless Use Cases)
From the Developing Serverless Solutions and Event-Driven Architectures + Deep Dive on AWS Lambda webinars, plus Q&A with Ebot.
What Lambda is under the hood
Section titled “What Lambda is under the hood”- Firecracker microVMs (KVM-based, ~125ms cold start), NOT
execor plain containers. - Each invocation = isolated microVM. Snapshot reuse = fast warm starts (~5ms).
- No state between invocations —
/tmpephemeral (10GB max), use S3/EFS/DynamoDB for persistence. - Container images supported (2020+, 10GB max) + Lambda Layers for shared deps (2018).
- Cold starts solved: Layers (cache), provisioned concurrency (warm), runtime snapshots (125ms).
- Max: 15 min timeout, 10GB memory, 1-1000 batch size for streams.
- Cost: $0.20/1M requests + $0.0000166667/GB-second. Free tier: 1M requests + 400K GB-s.
When to use Lambda (the decision tree)
Section titled “When to use Lambda (the decision tree)”| Use case | Lambda? | Why |
|---|---|---|
| Chaining AWS services (glue) | ✅ | Native triggers: S3, DynamoDB Streams, SQS, SNS, API Gateway, EventBridge |
| Seasonal/bursty events (ads, campaigns) | ✅ | Pay-per-invocation, idle = $0. Spikes cost pennies vs always-on EC2 |
| Scheduled tasks (cron) | ✅ | EventBridge schedule → Lambda. < 15 min runtime |
| Monthly backup of custom data | ✅ | Only if AWS has NO native backup for that data (RDS/DynamoDB/S3 have built-ins) |
| User-facing APIs | ⚠️ | Works for low traffic; API Gateway → Lambda. High RPS → EC2/ECS cheaper |
| Spike offload (EC2 stays baseline) | ✅ | ALB/SQS routes overflow → Lambda absorbs burst, EC2 never scales up |
| Long-running (>15 min) | ❌ | Hard timeout. Use ECS/Fargate |
| Steady high traffic (>1000 RPS) | ❌ | EC2/Fargate cheaper per hour |
| WebSocket/long connections | ❌ | Lambda freezes after response. Use Fargate |
| Heavy CPU/GPU (ML, video encoding) | ❌ | 15 min + 10GB memory ceiling |
Lambda ≠ just chaining
Section titled “Lambda ≠ just chaining”- Lambda = the worker (executes logic)
- SNS/SQS/EventBridge = the pipes (routes data)
- Step Functions = the manager (decides what runs next)
- n8n = all-in-one workflow tool (400+ SaaS integrations); Step Functions + Lambda = AWS-native equivalent. n8n for business automation, SFN for cloud infra.
Spike offload pattern (EC2 baseline + Lambda overflow)
Section titled “Spike offload pattern (EC2 baseline + Lambda overflow)”User → API Gateway/ALB → EC2 (normal load) └── overflow → SQS → Lambda (auto-scales)- EC2 stays at normal capacity; SQS absorbs burst; Lambda drains queue.
- Alternative: ALB priority rules (weight X% EC2 / Y% Lambda, date-based).
- Use when: predictable spike date, burst tolerant to slight delay, logic splittable.
- Not when: unpredictable spikes, cold-start-intolerant, single codebase.
Seasonal economics
Section titled “Seasonal economics”EC2 t3.small always-on: $15.18/month (mostly idle)Fargate always-on: $8.90/month (mostly idle)Lambda 1M invocations: ~$1.87 for the burstServerless portfolio (from Developing Serverless video)
Section titled “Serverless portfolio (from Developing Serverless video)”Compute: Lambda, Fargate | API: API Gateway | Storage: S3 | DB: DynamoDB, Aurora Serverless | Auth: Cognito | Messaging: SNS, SQS | Orchestration: Step Functions | Analytics: Kinesis, Athena
Video sources
Section titled “Video sources”Meeting Recording - Webinar - Developing Serverless Solutions and Event-Driven Architectures.mp4(serverless portfolio, patterns, composition)Meeting+Recording+-+Webinar+-+Deep+Dive+on+AWS+Lambda+++Demo.mp4(Firecracker, layers, cold start)Meeting Recording - Webinar - Introducing Lambda.mp4
Related
Section titled “Related”- [[aws/index]]
- [[aws/dynamodb-deep-dive/index]]
Lambda vs Express endpoint (mental model)
Section titled “Lambda vs Express endpoint (mental model)”Same shape, different runtime:
Express (you own the server): EC2 → Node.js → your handler (always running)
Lambda (AWS owns the server): AWS → Firecracker microVM → your handler (spawns per invocation)Code comparison
Section titled “Code comparison”// Expressapp.post('/users', async (req, res) => { const { name, email } = req.body; const userId = uuid(); await db.query('INSERT...', [userId, name, email]); res.json({ userId, name, email });});# Lambda (API Gateway trigger)def handler(event, context): body = json.loads(event.get('body', '{}')) user_id = context.aws_request_id # unique per invocation dynamodb.put_item(Item={'user_id': user_id, **body}) return {'statusCode': 201, 'body': json.dumps({'user_id': user_id, **body})}What doesn’t transfer
Section titled “What doesn’t transfer”- ❌ In-memory state (cache, sessions) — use ElastiCache / DynamoDB
- ❌ Long-running >15 min — use ECS/Fargate
- ❌ WebSocket connections — use API Gateway WebSocket + Lambda or Fargate
- ⚠️ Module-scope init runs once per microVM lifecycle, not per invocation
What does transfer
Section titled “What does transfer”- Handler function shape (
app.post(path, fn)↔def handler(event, context)) - Input/output (req.body → event.body, res.json → return {statusCode, body})
- Middleware (logging, auth) via Lambda Powertools or inline
- Environment variables (Lambda env vars)
- Tests (pytest +
motofor AWS mocking)
End-to-end creation (minimal flow)
Section titled “End-to-end creation (minimal flow)”# 1. Write handlercat > index.py << 'EOF'import jsondef handler(event, context): return {'statusCode': 200, 'body': json.dumps({'hello': 'lambda'})}EOF
# 2. Zipzip function.zip index.py
# 3. Create role + function (via CLI or Console)aws iam create-role --role-name lambda-exec-role \ --assume-role-policy-document '{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Principal":{"Service":"lambda.amazonaws.com"},"Action":"sts:AssumeRole"}]}'aws iam attach-role-policy --role-name lambda-exec-role \ --arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRoleaws lambda create-function --function-name my-handler \ --runtime python3.12 --handler index.handler \ --role arn:aws:iam::ACCOUNT_ID:role/lambda-exec-role \ --zip-file fileb://function.zip
# 4. Trigger via API Gateway (HTTP API = cheapest)# Console: API Gateway → Create HTTP API → add Lambda integration → deploy# Or via SAM:# template.yaml with Type: HttpApi + Path: /hello + Method: postProduction-ready pattern (with error handling + DynamoDB)
Section titled “Production-ready pattern (with error handling + DynamoDB)”import json, logging, boto3logger = logging.getLogger(); logger.setLevel(logging.INFO)ddb = boto3.resource('dynamodb').Table('Users')
def handler(event, context): try: body = json.loads(event.get('body', '{}')) if not body.get('name') or not body.get('email'): return {'statusCode': 400, 'body': json.dumps({'error': 'name + email required'})} user_id = context.aws_request_id ddb.put_item(Item={'user_id': user_id, **body, 'created_at': context.timestamp}) return {'statusCode': 201, 'headers': {'Content-Type':'application/json'}, 'body': json.dumps({'user_id': user_id, **body})} except Exception: logger.exception("create_user failed") return {'statusCode': 500, 'body': json.dumps({'error': 'internal'})}Triggers (from backend code)
Section titled “Triggers (from backend code)”# Sync invokeboto3.client('lambda').invoke( FunctionName='my-handler', InvocationType='RequestResponse', Payload=json.dumps({'user_id': 'alice'}))
# Async (fire-and-forget)boto3.client('lambda').invoke( FunctionName='my-handler', InvocationType='Event', Payload=json.dumps({...}))
# Polled event source (Lambda polls SQS/Kinesis/DynamoDB Streams)boto3.client('lambda').create_event_source_mapping( FunctionName='my-handler', EventSourceArn='arn:aws:sqs:...:my-queue', BatchSize=10, StartingPosition='TRIM_HORIZON')Deploy with SAM (CloudFormation-lite)
Section titled “Deploy with SAM (CloudFormation-lite)”AWSTemplateFormatVersion: '2010-09-09'Transform: AWS::Serverless-2016-10-31Resources: MyHandler: Type: AWS::Serverless::Function Properties: CodeUri: ./src Handler: index.handler Runtime: python3.12 MemorySize: 256 Timeout: 10 Events: HelloApi: Type: HttpApi Properties: {Path: /hello, Method: post} Policies: - DynamoDBCrudPolicy: TableName: Userssam build && sam deploy --guided