Skip to content

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.

  • Firecracker microVMs (KVM-based, ~125ms cold start), NOT exec or plain containers.
  • Each invocation = isolated microVM. Snapshot reuse = fast warm starts (~5ms).
  • No state between invocations — /tmp ephemeral (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.
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 = 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.
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 burst

Serverless 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

  • 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
  • [[aws/index]]
  • [[aws/dynamodb-deep-dive/index]]

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)
// Express
app.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})}
  • ❌ 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
  • 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 + moto for AWS mocking)
Terminal window
# 1. Write handler
cat > index.py << 'EOF'
import json
def handler(event, context):
return {'statusCode': 200, 'body': json.dumps({'hello': 'lambda'})}
EOF
# 2. Zip
zip 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/AWSLambdaBasicExecutionRole
aws 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: post

Production-ready pattern (with error handling + DynamoDB)

Section titled “Production-ready pattern (with error handling + DynamoDB)”
import json, logging, boto3
logger = 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'})}
# Sync invoke
boto3.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')
template.yaml
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Resources:
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: Users
Terminal window
sam build && sam deploy --guided