Saltar para o conteúdo
Cold Start no Lambda: Causas, Impacto e Como Mitigar
AWS

Cold Start no Lambda: Causas, Impacto e Como Mitigar

10 de novembro de 2024·Paulo de Paula

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:

  1. Download do pacote — baixa seu código do S3 ou ECR
  2. Inicialização do runtime — inicia o interpretador (Node.js, Python, Java…)
  3. Execução do init code — tudo fora do handler é executado uma vez
  4. 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 aqui

Causas 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.js

2. 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 quentes

Ou 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 runtime

Benchmarks por runtime (sem provisioned concurrency)

RuntimeCold Start Típico
Node.js 20.x150–400ms
Python 3.12100–300ms
Java 21 (com SnapStart)< 1s
Java 21 (sem SnapStart)3–10s
.NET 8200–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.