Lambda Performance & Best Practices
Cold starts, connection reuse, error handling, and VPC gotchas are the main operational pain points for Lambda in production. Addressing these systematically leads to faster, cheaper, and more reliable serverless functions.
Cold Start Optimization
// Cold start contributors (in order of impact):
// 1. Deployment package size — trim dependencies aggressively
// 2. Runtime — Node.js/Python faster than Java/Kotlin
// 3. VPC attachment — adds 100-500ms for ENI setup (use Lambda SnapStart for Java)
// 4. Memory — higher memory = more CPU = faster init
// 5. SDK imports — use modular @aws-sdk/client-* not the monolithic aws-sdk v2
// BAD — imports entire AWS SDK (~40 MB in v2)
// const AWS = require("aws-sdk");
// const s3 = new AWS.S3();
// GOOD — import only the clients you need (tree-shakeable)
import { S3Client, GetObjectCommand } from "@aws-sdk/client-s3";
import { DynamoDBDocumentClient, GetCommand } from "@aws-sdk/lib-dynamodb";
// Minimize dependencies — use npm analyze
// npx webpack-bundle-analyzer stats.json
// npx esbuild src/index.ts --bundle --platform=node --minify --outfile=dist/index.js
// Use esbuild for ultra-fast bundling (smaller output than webpack)
// package.json build script:
// "build": "esbuild src/index.ts --bundle --platform=node --target=node20 --outfile=dist/index.js"
// Provisioned Concurrency (eliminates cold starts for latency-sensitive functions)
// Applied to an alias or version — pre-warms N execution environments
// Cost: charged per provisioned-concurrency-hours regardless of invocations
// Use Lambda Power Tuning to find optimal memory/PC configuration:
// https://github.com/alexcasalboni/aws-lambda-power-tuning
// Right-size memory using Lambda Power Tuning State Machine (Step Functions)
// Invoke the state machine with:
// { "lambdaARN": "arn:...", "powerValues": [128,256,512,1024,2048],
// "num": 50, "payload": {}, "parallelInvocation": true }Connection Reuse & VPC Gotchas
// Database connection reuse — initialize OUTSIDE the handler
// Connection is reused across warm invocations (same execution environment)
import { Pool } from "pg";
// Created once per cold start, reused across warm invocations
const pool = new Pool({
host: process.env.DB_HOST,
database: process.env.DB_NAME,
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
max: 2, // Keep small — Lambda can have many concurrent instances
idleTimeoutMillis: 0,
connectionTimeoutMillis: 2000,
});
export const handler = async (event) => {
const client = await pool.connect();
try {
const result = await client.query("SELECT * FROM users WHERE id = $1", [event.userId]);
return result.rows[0];
} finally {
client.release();
}
};
// RDS Proxy — use instead of direct DB connections for Lambda
// Pools connections at the proxy level, handles Lambda burst scaling
// Eliminates "too many connections" errors when Lambda scales to hundreds of instances
// Add RDS_PROXY_ENDPOINT to environment variables and connect to it instead
// VPC Lambda gotchas:
// - Adds ~100ms cold start (ENI allocation) — eliminated in 2019 with Hyperplane ENI
// but VPC DNS resolution can still add latency
// - Lambda in a private subnet needs NAT Gateway for internet access
// (costs ~$0.045/hr + data processing)
// - Lambda NOT in VPC cannot access RDS/ElastiCache in VPC
// - Use VPC endpoints (interface/gateway) to access AWS services without NAT GW:
// S3, DynamoDB gateway endpoints are free; SQS, SNS, etc. cost $0.01/hrError Handling — DLQ & Destinations
# Retry behavior by invocation type:
# Synchronous (API GW, ALB): no retry — caller gets the error immediately
# Asynchronous (S3, SNS, EventBridge): 2 automatic retries with exponential backoff
# Event source mapping (SQS, Kinesis, DynamoDB Streams): retries until message expires or batch succeeds
# Dead Letter Queue (DLQ) — for async invocations only
# Failed events (after retries) go to SQS queue or SNS topic
aws lambda update-function-configuration --function-name my-function --dead-letter-config "TargetArn=arn:aws:sqs:us-east-1:123:my-dlq"
# Lambda Destinations (preferred over DLQ — more flexible, supports success + failure)
aws lambda put-function-event-invoke-config --function-name my-function --maximum-retry-attempts 2 --maximum-event-age-in-seconds 3600 --destination-config '{
"OnSuccess": {"Destination": "arn:aws:sqs:us-east-1:123:success-queue"},
"OnFailure": {"Destination": "arn:aws:sqs:us-east-1:123:dlq"}
}'
# SQS event source: partial batch failure (return failed message IDs)
# Enable "Report batch item failures" on the event source mapping
aws lambda update-event-source-mapping --uuid <mapping-id> --function-response-types ReportBatchItemFailures
# Structured logging for easier CloudWatch Insights queries
import { Logger } from "@aws-lambda-powertools/logger";
const logger = new Logger({ serviceName: "user-service", logLevel: "INFO" });
export const handler = async (event) => {
logger.addContext(context);
logger.info("Processing request", { userId: event.userId, action: "getUser" });
// CloudWatch Insights query: fields @timestamp, userId, action | filter level = "INFO"
};
# Lambda Powertools for TypeScript/Python (AWS-maintained utility library)
# Provides: structured logging, tracing (X-Ray), metrics (CloudWatch EMF), parameters, batch
npm install @aws-lambda-powertools/logger @aws-lambda-powertools/tracer @aws-lambda-powertools/metricsKeep your own version of these notes — editable, searchable, and organised by your stack.
Start free