Cold Start no Lambda: Causas, Impacto e Como Mitigar
Cold start é o custo de inicializar um contêiner de execução novo no Lambda. Dependendo do runtime e do tamanho do pacote, pode adicionar centenas de milissegundos — ou segundos — à resposta do usuário.
O que acontece num cold start?
Quando o Lambda não tem um contêiner quente disponível, ele executa:
- Download do pacote — baixa seu código do S3 ou ECR
- Inicialização do runtime — inicia o interpretador (Node.js, Python, Java…)
- Execução do init code — tudo fora do handler é executado uma vez
- Execução do handler — sua função roda de verdade
Etapas 1-3 são o cold start. Etapa 4 é o que você paga sempre.
Medindo o cold start
O CloudWatch mostra Init Duration separado da Duration nos logs:
REPORT RequestId: abc-123
Duration: 45.23 ms
Billed Duration: 46 ms
Memory Size: 512 MB
Max Memory Used: 89 MB
Init Duration: 312.45 ms ← cold start aquiCausas do cold start lento
1. Pacote grande
Cada MB a mais demora para baixar e inicializar:
# Verifique o tamanho real do seu pacote
du -sh .aws-sam/build/MinhaFuncao/
# Idealmente: < 5 MB para Node.js2. Imports pesados no nível do módulo
// Ruim — importa o SDK inteiro no cold start
const AWS = require('aws-sdk')
// Bom — import granular
const { DynamoDBClient } = require('@aws-sdk/client-dynamodb')
const { GetItemCommand } = require('@aws-sdk/client-dynamodb')3. Conexões de banco no init code
// Ruim — conecta no cold start mesmo se não precisar
const db = new Pool({ connectionString: process.env.DB_URL })
// Bom — lazy initialization
let db
function getDb() {
if (!db) {
db = new Pool({ connectionString: process.env.DB_URL })
}
return db
}Estratégias de mitigação
Provisioned Concurrency
Mantém N instâncias sempre quentes. Custo extra, mas zero cold start:
# serverless.yml
functions:
api:
handler: handler.main
provisionedConcurrency: 5 # 5 instâncias sempre quentesOu via CDK:
const fn = new lambda.Function(this, 'ApiFunction', {
runtime: lambda.Runtime.NODEJS_20_X,
handler: 'index.handler',
code: lambda.Code.fromAsset('src'),
})
const alias = new lambda.Alias(this, 'ProdAlias', {
aliasName: 'prod',
version: fn.currentVersion,
provisionedConcurrentExecutions: 5,
})Lambda Snapstart (Java e agora Node.js 18+)
O SnapStart tira um snapshot do estado inicializado e restaura em vez de reinicializar:
# CDK
const fn = new lambda.Function(this, 'Fn', {
runtime: lambda.Runtime.NODEJS_18_X,
snapStart: lambda.SnapStartConf.ON_PUBLISHED_VERSIONS,
// ...
})Warm-up com EventBridge
Pinga suas funções regularmente para manter instâncias quentes:
// CDK
new events.Rule(this, 'WarmupRule', {
schedule: events.Schedule.rate(Duration.minutes(5)),
targets: [new targets.LambdaFunction(minhaFuncao, {
event: events.RuleTargetInput.fromObject({ warmup: true }),
})],
})No handler:
exports.handler = async (event) => {
if (event.warmup) return { statusCode: 200, body: 'warm' }
// ... lógica real
}Reduzir tamanho do pacote
# Análise de dependências com source-map-explorer
npm run build
npx source-map-explorer dist/bundle.js
# esbuild para bundling eficiente
esbuild src/index.ts \
--bundle \
--platform=node \
--target=node20 \
--minify \
--outfile=dist/index.js \
--external:@aws-sdk/* # SDK já está disponível no runtimeBenchmarks por runtime (sem provisioned concurrency)
| Runtime | Cold Start Típico |
|---|---|
| Node.js 20.x | 150–400ms |
| Python 3.12 | 100–300ms |
| Java 21 (com SnapStart) | < 1s |
| Java 21 (sem SnapStart) | 3–10s |
| .NET 8 | 200–500ms |
Quando o cold start realmente importa?
- APIs síncronas voltadas ao usuário — sim, otimize
- Background jobs assíncronos — geralmente não importa
- Webhooks — depende do SLA do serviço que chama
Para APIs críticas com p99 < 200ms, Provisioned Concurrency é quase sempre a resposta certa. Para todo o resto, otimizar o pacote já resolve bastante.