| name | serverless-patterns |
| description | Serverless patterns: cold start optimization (Provisioned Concurrency, SnapStart, keep-warm), event source mapping (S3/SQS/DynamoDB Streams/EventBridge), AWS Step Functions, Lambda Powertools (logging/metrics/tracing), idempotency, cost model, and observability with X-Ray. |
Serverless Patterns
Production patterns for AWS Lambda and serverless architectures — cold starts, event routing, orchestration, and observability.
When to Activate
- Diagnosing Lambda cold start latency
- Designing event-driven workflows (S3 → Lambda, SQS batch processing)
- Building Step Functions state machines
- Adding structured logging/tracing with Lambda Powertools
- Calculating serverless cost vs. container cost
- Implementing idempotency for at-least-once delivery
- Choosing between Provisioned Concurrency, SnapStart, or keep-warm pings for a latency-sensitive function
- Debugging partial batch failures in SQS or DynamoDB Streams event source mappings
Cold Start Deep Dive
What Causes Cold Starts
Cold Start = Container Boot + Runtime Init + Function Init
~100-500ms + ~50-200ms + your code
Warm Start = (container already running)
~1-10ms (just your function code)
Cold Start by Runtime (approximate):
| Runtime | Cold Start |
|---|
| Rust (custom runtime) | ~1ms |
| Go | ~5ms |
| Node.js 20 | ~80–150ms |
| Python 3.12 | ~100–300ms |
| Java 21 (without SnapStart) | ~1000–3000ms |
| Java 21 (with SnapStart) | ~100–200ms |
Mitigation Strategies
1. Provisioned Concurrency (eliminates cold starts)
ProvisionedConcurrencyConfig:
ProvisionedConcurrentExecutions: 5
aws application-autoscaling register-scalable-target \
--service-namespace lambda \
--resource-id function:my-function:prod \
--scalable-dimension lambda:function:ProvisionedConcurrency
aws application-autoscaling put-scheduled-action \
--service-namespace lambda \
--resource-id function:my-function:prod \
--scalable-dimension lambda:function:ProvisionedConcurrency \
--scheduled-action-name scale-up-morning \
--schedule "cron(0 7 * * ? *)" \
--scalable-target-action MinCapacity=10,MaxCapacity=10
2. SnapStart (Java only — Firecracker MicroVM snapshot)
MyFunction:
Type: AWS::Serverless::Function
Properties:
SnapStart:
ApplyOn: PublishedVersions
AutoPublishAlias: prod
3. Keep-Warm Pings (free mitigation)
export const handler = async (event: any) => {
if (event.source === 'keep-warm') {
return { statusCode: 200 };
}
return handleRealRequest(event);
};
KeepWarmRule:
Type: AWS::Events::Rule
Properties:
ScheduleExpression: rate(5 minutes)
Targets:
- Id: MyLambdaTarget
Arn: !GetAtt MyFunction.Arn
Input: '{"source": "keep-warm"}'
4. Minimize Package Size
aws lambda get-function --function-name my-function \
--query 'Configuration.CodeSize'
esbuild src/handler.ts --bundle --minify --target=node20 \
--platform=node --outfile=dist/handler.js
5. Move Initialization Outside Handler
export const handler = async (event) => {
const db = new Database(process.env.DB_URL);
return db.query('SELECT ...');
};
const db = new Database(process.env.DB_URL);
export const handler = async (event) => {
return db.query('SELECT ...');
};
Event Source Mapping
S3 → Lambda
interface S3Event {
Records: Array<{
s3: {
bucket: { name: string };
object: { key: string; size: number; eTag: string };
};
eventName: string;
}>;
}
export const handler = async (event: S3Event) => {
for (const record of event.Records) {
const { bucket, object } = record.s3;
const key = decodeURIComponent(object.key.replace(/\+/g, ' '));
const file = await s3.getObject({
Bucket: bucket.name,
Key: key,
}).promise();
await processFile(file.Body as Buffer, key);
}
};
SQS → Lambda (Batch Processing)
import { SQSEvent, SQSBatchResponse } from 'aws-lambda';
export const handler = async (event: SQSEvent): Promise<SQSBatchResponse> => {
const failures: SQSBatchResponse['batchItemFailures'] = [];
await Promise.all(
event.Records.map(async (record) => {
try {
const message = JSON.parse(record.body);
await processMessage(message);
} catch (error) {
failures.push({ itemIdentifier: record.messageId });
}
})
);
return { batchItemFailures: failures };
};
MyFunction:
Events:
SQSQueue:
Type: SQS
Properties:
Queue: !GetAtt MyQueue.Arn
BatchSize: 10
FunctionResponseTypes:
- ReportBatchItemFailures
MaximumBatchingWindowInSeconds: 5
DynamoDB Streams → Lambda (CDC)
import { DynamoDBStreamEvent, DynamoDBRecord } from 'aws-lambda';
import { unmarshall } from '@aws-sdk/util-dynamodb';
export const handler = async (event: DynamoDBStreamEvent) => {
for (const record of event.Records) {
const eventType = record.eventName;
if (eventType === 'INSERT' || eventType === 'MODIFY') {
const newItem = unmarshall(record.dynamodb?.NewImage ?? {});
await syncToSearchIndex(newItem);
}
if (eventType === 'REMOVE') {
const oldItem = unmarshall(record.dynamodb?.OldImage ?? {});
await removeFromSearchIndex(oldItem.id);
}
}
};
EventBridge → Lambda
interface OrderPlacedEvent {
source: string;
'detail-type': string;
detail: {
orderId: string;
customerId: string;
amount: number;
};
}
export const handler = async (event: EventBridge.EventBridgeEvent<'Order Placed', OrderDetail>) => {
const { orderId, customerId, amount } = event.detail;
await processOrder({ orderId, customerId, amount });
};
OrderRule:
Type: AWS::Events::Rule
Properties:
EventPattern:
source: ['myapp.orders']
detail-type: ['Order Placed']
detail:
amount: [{ numeric: ['>', 100] }]
Targets:
- Id: ProcessLargeOrder
Arn: !GetAtt ProcessOrderFunction.Arn
AWS Step Functions
State Machine Types
| Type | Duration | Pricing | Use Case |
|---|
| Standard | Up to 1 year | Per state transition | Long-running workflows, auditing |
| Express (Async) | Up to 5 minutes | Per invocation + duration | High-volume events |
| Express (Sync) | Up to 5 minutes | Per invocation + duration | Request-response |
State Types
{
"Comment": "Order processing workflow",
"StartAt": "ValidateOrder",
"States": {
"ValidateOrder": {
"Type": "Task",
"Resource": "arn:aws:lambda:...:function:ValidateOrder",
"Next": "ChargePayment",
"Catch": [{
"ErrorEquals": ["ValidationError"],
"Next": "HandleValidationError"
}],
"Retry": [{
"ErrorEquals": ["States.TaskFailed"],
"IntervalSeconds": 2
Map State (Fan-Out)
{
"ProcessItems": {
"Type": "Map",
"ItemsPath": "$.items",
"MaxConcurrency": 10,
"Iterator": {
"StartAt": "ProcessItem",
"States": {
"ProcessItem": {
"Type": "Task",
"Resource": "arn:aws:lambda:...:function:ProcessItem",
"End": true
}
}
},
"Next": "Aggregate"
}
}
Lambda Powertools
Unified library for structured logging, metrics, and tracing:
npm install @aws-lambda-powertools/logger @aws-lambda-powertools/metrics @aws-lambda-powertools/tracer
Logger
import { Logger } from '@aws-lambda-powertools/logger';
const logger = new Logger({
serviceName: 'order-service',
logLevel: 'INFO',
});
export const handler = async (event: any) => {
logger.info('Processing order', { orderId: event.orderId });
try {
const result = await processOrder(event.orderId);
logger.info('Order processed', { orderId: event.orderId, result });
return result;
} catch (error) {
logger.error('Order processing failed', { error, orderId: event.orderId });
throw error;
}
};
Metrics
import { Metrics, MetricUnit } from '@aws-lambda-powertools/metrics';
const metrics = new Metrics({ namespace: 'OrderService', serviceName: 'order-processor' });
export const handler = async (event: any) => {
metrics.addMetric('OrdersReceived', MetricUnit.Count, 1);
const start = Date.now();
await processOrder(event);
metrics.addMetric('ProcessingTime', MetricUnit.Milliseconds, Date.now() - start);
metrics.addMetric('OrdersSucceeded', MetricUnit.Count, 1);
metrics.publishStoredMetrics();
};
Tracer (X-Ray)
import { Tracer } from '@aws-lambda-powertools/tracer';
const tracer = new Tracer({ serviceName: 'order-service' });
export const handler = async (event: any) => {
const segment = tracer.getSegment();
const subsegment = segment?.addNewSubsegment('processOrder');
try {
const result = await processOrder(event);
tracer.putAnnotation('orderId', event.orderId);
tracer.putMetadata('result', result);
return result;
} catch (error) {
subsegment?.addError(error as Error);
throw error;
} finally {
subsegment?.close();
}
};
For idempotency (Lambda Powertools idempotency, DynamoDB persistence), cost model (Lambda vs. Container break-even), and observability (CloudWatch Insights queries), see skill serverless-patterns-advanced.
Reference
edge-patterns — Cloudflare Workers, Vercel Edge Middleware (CPU-constrained, no cold starts)
observability — General observability patterns (OpenTelemetry, Grafana)
chaos-engineering — Testing Lambda failure modes (throttling, DLQ exhaustion)