Pular para o conteúdo

AWS · parte 6 de 11

Lambda ou EC2: quando usar funções sob demanda

Quando preferir o Lambda ao EC2 para rodar código sob demanda, o que muda no modelo de cobrança, na escala e nas limitações.

Revisada em · 22 min de leitura

Tamanho da fonte:
Largura da coluna:

O AWS Lambda é um serviço de computação “serverless” (sem servidor) oferecido pela Amazon Web Services. Você envia o código de uma função, e a AWS a executa sempre que um evento acontece, sem que você precise comprar, alugar ou administrar a máquina onde ela roda.

A ideia parece simples, mas o que muda na prática é grande. Esta nota começa pelo conceito e pelas razões de existir do serviço. Depois, na seção “como usar de verdade”, ela mostra uma função escrita em Go e em TypeScript, os comandos da AWS CLI para criá-la e ligá-la a gatilhos, os erros mais comuns e como resolvê-los.

1. O conceito “serverless”

Apesar do nome, existem servidores físicos por trás do Lambda. A diferença é que você não precisa se preocupar com eles. A AWS cuida do provisionamento da máquina, das atualizações do sistema operacional, dos patches de segurança e da manutenção do hardware.

Sua responsabilidade fica restrita ao código, a lógica do seu negócio, e à configuração da função. O resto é trabalho da plataforma. Isso não elimina o trabalho de operação, só o desloca: você continua responsável por permissões, por limites, por custos e pelo que a função faz com os bancos e serviços que ela chama.

2. Execução baseada em eventos

O Lambda foi projetado para reagir a eventos. A função fica sem rodar até que algo a acione.

aws lambda event driven
figura 1 · aws lambda event driven

Uma função pode ser disparada pelo upload de um arquivo no S3, por uma atualização numa tabela do DynamoDB, por uma requisição HTTP via API Gateway ou por mensagens de uma fila SQS. O desenho acima mostra as três entradas chegando na mesma função e as três saídas partindo dela. A seta de cada entrada leva um rótulo com o formato do dado que viaja, e isso ajuda a lembrar que cada gatilho entrega um payload diferente para o seu código.

Um exemplo típico: quando um usuário envia uma foto, o Lambda acorda, gera uma miniatura, grava o resultado num bucket e volta a ficar parado.

3. Escalabilidade automática

Diferente do EC2, onde você configura regras para aumentar ou diminuir a capacidade, o Lambda cria ambientes de execução conforme a demanda. Se chegam 1 ou 1.000 requisições simultâneas, a plataforma executa o código em tantos ambientes quanto forem necessários, até o limite de concorrência da sua conta.

Esse limite importa mais do que parece. A cota padrão de execuções simultâneas é de 1.000 por região, e contas novas começam com cotas reduzidas que a AWS aumenta conforme o uso. Se a sua função chama um banco que aguenta 100 conexões, 1.000 ambientes podem derrubar o banco antes de o Lambda atingir o próprio limite. A seção sobre RDS Proxy mostra como lidar com isso.

4. Modelo de cobrança

O custo é calculado pelo uso:

  • Tempo de execução: você paga pela duração medida em milissegundos, multiplicada pela memória configurada.
  • Número de requisições: cada invocação tem um preço fixo.
  • Sem custo ocioso: se a função não está rodando, você não paga por ela. Não existe a cobrança por hora de “servidor ligado” que existe no EC2.

Os preços listados na página oficial de preços, para x86 e região de referência, são US$ 0,20 por milhão de requisições e US$ 0,0000166667 por GB-segundo, com franquia mensal de 1 milhão de requisições e 400.000 GB-segundos. Os preços mudam com o tempo e variam por arquitetura. O valor para arm64 não foi confirmado nesta revisão, então consulte a página de preços antes de fazer contas.

Resumo: para que serve?

O AWS Lambda é usado para construir aplicações serverless, como backends de sites e aplicativos, processamento de dados em tempo real, automação de infraestrutura e microsserviços. O desenvolvedor foca no código, e a AWS cuida da disponibilidade e da escala.

Quando devo preferir o Lambda ao invés do EC2?

Prefira o AWS Lambda ao Amazon EC2 quando o seu objetivo é focar na lógica de negócio, sem gerenciar servidores, atualizações de sistema operacional ou escala manual.

1. Cargas de trabalho imprevisíveis ou esporádicas

Se o tráfego varia muito ou a aplicação fica ociosa por longos períodos, o Lambda costuma sair mais barato. Você paga pelo tempo de computação consumido e pelo número de requisições. No EC2, você paga pela instância enquanto ela estiver ligada, mesmo sem processar nada.

2. Execução baseada em eventos

O Lambda é ideal para tarefas que precisam acontecer em resposta a uma ação dentro da AWS. Processar arquivos assim que chegam no S3, reagir a mudanças no DynamoDB ou processar fluxos de cliques e logs via Amazon Kinesis são exemplos comuns. Você não precisa manter um servidor ligado 24 horas esperando o evento.

3. Aplicações serverless e microsserviços

Com o Lambda atrás do API Gateway, cada rota da API pode disparar uma função específica. A AWS cuida da alta disponibilidade e do dimensionamento do ambiente de execução.

4. Tarefas curtas

O Lambda foi feito para execuções rápidas e efêmeras. Um script que faz uma transformação de dados, envia uma notificação ou valida algo e termina em segundos ou minutos encaixa bem. Processos de horas ou dias, ou que dependem de estado complexo na memória, pedem outra ferramenta, como o EC2 ou contêineres.

Resumo da decisão

Escolha o Lambda quando você quer não gerenciar servidores, ter escala automática, pagar por execução e integrar eventos nativamente com outros serviços AWS.

Como usar de verdade

Até aqui o texto explicou o modelo. Esta parte mostra o que você faz no dia a dia: criar a função, ligá-la a um gatilho, passar configuração e segredos, e diagnosticar quando algo dá errado.

O que a função precisa para existir

Uma função Lambda tem cinco decisões que você toma na criação:

  • Runtime: o ambiente que executa o código. Para Node.js, Python, Java e outros há runtimes gerenciados. Para Go, o caminho é um runtime do tipo “OS-only”, o provided.al2023, que executa um binário.
  • : a função que a plataforma chama. Em Go, o handler é a função passada para lambda.Start.
  • Role de execução: uma role do IAM que diz o que a função pode fazer. Sem permissão para ler um bucket, a chamada ao S3 falha, mesmo que o código esteja certo.
  • Memória: entre 128 MB e 10.240 MB, em incrementos de 1 MB. A CPU é proporcional à memória: 1.769 MB equivalem a um vCPU. Aumentar a memória costuma reduzir o tempo de execução, e como você paga por GB-segundo, a conta pode até ficar mais barata.
  • Timeout: o máximo é de 900 segundos (15 minutos). Um timeout baixo demais mata invocações que deveriam terminar. Um alto demais mascara travamentos.

Alguns limites valem ser conhecidos antes de projetar qualquer coisa. O payload síncrono aceita 6 MB na entrada e na saída; o assíncrono aceita 1 MB. As variáveis de ambiente somam no máximo 4 KB. O pacote zip pode ter 50 MB compactado enviado pela API, e 250 MB descompactado. Quando o pacote passa do limite de envio direto, ele vai antes para o S3.

Escrevendo uma função em Go

Em Go, o código compila para um binário nativo, e a plataforma precisa encontrar um executável chamado bootstrap na raiz do zip. O runtime provided.al2023 cuida de iniciar esse binário e de conversar com o Lambda pela Runtime API. Você não instala Go no servidor, só entrega o binário pronto.

O handler aceita uma das assinaturas previstas pela biblioteca, por exemplo func(context.Context, TIn) (TOut, error). O tipo de entrada é um struct que você define ou um tipo de evento pronto, como events.SQSEvent.

Este exemplo processa um lote de mensagens de uma fila SQS. Ele separa a regra de negócio, em processa, do handler, o que deixa a regra fácil de testar sozinha.

package main

import (
	"context"
	"encoding/json"
	"fmt"
	"log"

	"github.com/aws/aws-lambda-go/events"
	"github.com/aws/aws-lambda-go/lambda"
)

type Pedido struct {
	ID    string `json:"id"`
	Valor int    `json:"valor"`
}

// processa contém a regra de negócio. Não sabe nada sobre Lambda nem SQS.
func processa(p Pedido) error {
	if p.Valor < 0 {
		return fmt.Errorf("valor negativo no pedido %s", p.ID)
	}
	log.Printf("pedido %s processado", p.ID)
	return nil
}

// handler recebe um lote. Se uma mensagem falhar, devolve só o identificador
// dela em batchItemFailures; as outras são apagadas da fila.
func handler(ctx context.Context, evento events.SQSEvent) (events.SQSEventResponse, error) {
	var falhas []events.SQSBatchItemFailure

	for _, msg := range evento.Records {
		var p Pedido
		if err := json.Unmarshal([]byte(msg.Body), &p); err != nil {
			falhas = append(falhas, events.SQSBatchItemFailure{ItemIdentifier: msg.MessageId})
			continue
		}
		if err := processa(p); err != nil {
			falhas = append(falhas, events.SQSBatchItemFailure{ItemIdentifier: msg.MessageId})
		}
	}

	return events.SQSEventResponse{BatchItemFailures: falhas}, nil
}

func main() {
	lambda.Start(handler)
}

Um teste unitário que chama handler com quatro mensagens, sendo duas válidas, uma com valor negativo e uma com JSON inválido, imprime esta saída:

{"batchItemFailures":[{"itemIdentifier":"m2"},{"itemIdentifier":"m3"}]}

Esse resultado é o que a fila precisa para saber quais mensagens reprocessar. A seção de SQS explica o mecanismo por trás dele.

Para compilar para arm64, a arquitetura Graviton da AWS, use:

GOOS=linux GOARCH=arm64 CGO_ENABLED=0 go build -tags lambda.norpc -o bootstrap ./cmd/sqs
zip function.zip bootstrap

A flag -tags lambda.norpc remove o componente RPC, usado só pelo runtime go1.x, que está descontinuado. Neste ambiente, o binário sem outras otimizações ficou com 9,3 MB para o handler de SQS e 15,4 MB para o handler de API, medidos localmente.

Crie a função e envie o zip:

aws lambda create-function \
  --function-name pedidos-sqs \
  --runtime provided.al2023 \
  --handler bootstrap \
  --architectures arm64 \
  --role arn:aws:iam::111122223333:role/lambda-pedidos \
  --zip-file fileb://function.zip \
  --timeout 30 \
  --memory-size 512

Os parâmetros importam. --runtime provided.al2023 diz ao Lambda para executar o binário sem um runtime de linguagem. --handler bootstrap segue a convenção do runtime customizado: o executável precisa se chamar bootstrap, e o handler pode ter qualquer nome, aqui o mesmo para não confundir. --architectures arm64 precisa bater com o GOARCH da compilação, senão o binário não roda. --role recebe o ARN da role de execução, e fileb:// indica que o zip é binário. --timeout e --memory-size são opcionais, mas é bom declará-los em vez de depender de padrões.

Para testar a invocação direta, passe o evento como JSON. Com AWS CLI v2, use --cli-binary-format raw-in-base64-out quando o payload for um JSON escrito direto na linha de comando:

aws lambda invoke \
  --function-name pedidos-sqs \
  --cli-binary-format raw-in-base64-out \
  --payload '{"Records":[{"messageId":"m1","body":"{\"id\":\"A1\",\"valor\":10}"}]}' \
  resposta.json

cat resposta.json

A saída do comando, em stdout, tem a forma {"StatusCode": 200, "ExecutedVersion": "$LATEST"}. O StatusCode diz se a plataforma executou a função, não se a regra de negócio deu certo. O resultado da função fica em resposta.json. Um StatusCode 200 com FunctionError na saída indica que a função lançou erro, e isso aparece no log.

Exemplo com API Gateway: handler HTTP e conexão com o banco

O caso mais comum de Lambda em produção é uma API. O API Gateway recebe GET /pedidos/{id}, chama a função e devolve a resposta HTTP. O código abaixo mostra um detalhe que decide se a aplicação aguenta carga: o pool de conexões é criado na fase de inicialização, fora do handler, e tem tamanho fixo.

package main

import (
	"context"
	"database/sql"
	"encoding/json"
	"log"
	"net/http"
	"os"
	"time"

	"github.com/aws/aws-lambda-go/events"
	"github.com/aws/aws-lambda-go/lambda"
	_ "github.com/jackc/pgx/v5/stdlib"
)

// db é criado uma vez por ambiente de execução, na fase Init.
// sql.Open não conecta de fato: a conexão nasce na primeira consulta.
var db *sql.DB

func init() {
	var err error
	// DATABASE_URL aponta para o endpoint do RDS Proxy.
	db, err = sql.Open("pgx", os.Getenv("DATABASE_URL"))
	if err != nil {
		log.Fatalf("configuração do banco inválida: %v", err)
	}
	// Cada ambiente abre no máximo 2 conexões. Com 300 ambientes ao mesmo
	// tempo, isso dá 600 conexões no proxy, não 300 vezes um pool padrão.
	db.SetMaxOpenConns(2)
	db.SetMaxIdleConns(2)
	db.SetConnMaxIdleTime(60 * time.Second)
	db.SetConnMaxLifetime(5 * time.Minute)
}

func handler(ctx context.Context, req events.APIGatewayV2HTTPRequest) (events.APIGatewayV2HTTPResponse, error) {
	id := req.PathParameters["id"]

	var valor int
	err := db.QueryRowContext(ctx, "SELECT valor FROM pedidos WHERE id = $1", id).Scan(&valor)
	if err == sql.ErrNoRows {
		return events.APIGatewayV2HTTPResponse{StatusCode: http.StatusNotFound}, nil
	}
	if err != nil {
		log.Printf("falha na consulta: %v", err)
		return events.APIGatewayV2HTTPResponse{StatusCode: http.StatusInternalServerError}, nil
	}

	corpo, _ := json.Marshal(map[string]any{"id": id, "valor": valor})
	return events.APIGatewayV2HTTPResponse{
		StatusCode: http.StatusOK,
		Headers:    map[string]string{"content-type": "application/json"},
		Body:       string(corpo),
	}, nil
}

func main() {
	lambda.Start(handler)
}

Este código compila para arm64 e foi conferido por compilação, mas não foi executado contra um banco real, então trate o SQL como ilustração. O ponto de atenção está no init. Como o handler usa db sem recriá-lo, uma invocação quente reaproveita as conexões abertas. E como o limite por ambiente é pequeno, o número total de conexões cresce com a concorrência, não com o número de requisições. O diagrama abaixo mostra essa relação numa função de API e numa de fila lendo o mesmo banco.

lambda 01 cenario api fila
figura 2 · lambda 01 cenario api fila

O desenho tem o cliente à esquerda no topo, a API e a fila alimentando duas funções na VPC, e as duas funções falando com o RDS Proxy, que é quem abre as poucas conexões reais no banco. A cor verde marca os dados, a cor terracota marca o código que você escreve e o RDS Proxy, e as setas tracejadas são dependências de configuração e de log, não tráfego de requisição. O segredo do banco vem do Secrets Manager. Essa separação aparece de propósito, porque é a arquitetura que evita os erros descritos na última seção.

Exemplo em TypeScript: trigger do S3

Para Node.js, o runtime nodejs24.x vem gerenciado pela AWS. O exemplo abaixo lê o evento de upload de um bucket e ignora o que não é CSV. Ele foi testado executando o arquivo TypeScript diretamente com o Node 24, que remove os tipos. Em produção você compila com tsc ou esbuild antes de empacotar.

import type { S3Event } from "aws-lambda";

// O S3 envia a chave codificada para URL: espaço vira "+" e acentos viram %XX.
export function chaveDoEvento(chaveCodificada: string): string {
  return decodeURIComponent(chaveCodificada.replace(/\+/g, " "));
}

export const handler = async (evento: S3Event): Promise<{ processados: number }> => {
  let processados = 0;

  for (const registro of evento.Records) {
    const bucket = registro.s3.bucket.name;
    const chave = chaveDoEvento(registro.s3.object.key);

    if (!chave.endsWith(".csv")) {
      console.log(`ignorado: s3://${bucket}/${chave}`);
      continue;
    }

    console.log(`processando s3://${bucket}/${chave}`);
    processados++;
  }

  return { processados };
};

A saída do teste, com um evento de duas chaves, foi:

relatorios/vendas janç.csv
processando s3://meu-bucket/relatorios/vendas janç.csv
ignorado: s3://meu-bucket/fotos/capa.png
{"processados":1}

A função de decodificação existe porque o S3 não manda a chave como você a digitou. Um arquivo chamado vendas janç.csv chega como vendas+jan%C3%A7.csv. Sem a conversão, o processando apareceria com a chave errada e a leitura do objeto com GetObject falharia.

Para que o S3 possa invocar a função, a função precisa de uma permissão na sua política baseada em recurso. O comando abaixo a cria, restringindo a origem ao bucket e à conta:

aws lambda add-permission \
  --function-name processa-csv \
  --statement-id s3-invoca-processa-csv \
  --action lambda:InvokeFunction \
  --principal s3.amazonaws.com \
  --source-arn arn:aws:s3:::meu-bucket \
  --source-account 111122223333

A notificação do próprio bucket, que diz ao S3 para enviar o evento, é configurada à parte, com aws s3api put-bucket-notification-configuration. Esse comando não foi executado nesta revisão.

Os três gatilhos: síncrono, assíncrono e

Nem todo gatilho se comporta do mesmo jeito, e confundir os modos é a origem de muitos bugs. O diagrama a seguir coloca os três caminhos chegando na mesma função.

lambda 02 tres gatilhos
figura 3 · lambda 02 tres gatilhos

Na chamada síncrona, o cliente espera a resposta. A API Gateway fica bloqueada até a função terminar, e se a função falhar, quem recebe o erro é o cliente. A seta de ida e volta do desenho mostra essa espera.

Na chamada assíncrona, como a que o S3 faz, a plataforma aceita o evento, devolve uma confirmação e executa a função depois. Se a função falha, o Lambda tenta de novo, e quando as tentativas acabam o evento vai para um destino de falha ou para uma fila de mensagens mortas. A documentação do S3 confirma que a invocação é assíncrona. O número padrão de tentativas não foi confirmado nesta revisão, então verifique a configuração da sua função em vez de depender de memória.

No mapeamento de fonte, como o do SQS, quem faz a chamada é o próprio Lambda. Ele fica consultando a fila, junta mensagens em lotes e invoca a função com um lote por vez. Você não recebe um evento por mensagem, e sim uma lista delas. É por isso que a função precisa saber processar um lote inteiro e informar quais itens falharam, assunto da próxima seção.

SQS: lotes, falha parcial e fila de mensagens mortas

O mapeamento de fonte do SQS segue regras que pegam desenvolvedores de surpresa. Por padrão, o lote é de até 10 mensagens. Uma fila padrão aceita lotes de até 10.000 mensagens, e uma fila FIFO aceita até 10. Para lotes maiores que 10, a janela de lote (MaximumBatchingWindowInSeconds) precisa ser de pelo menos 1 segundo. Com tráfego baixo, o Lambda pode esperar até 20 segundos antes de invocar a função, mesmo com janela menor que isso.

O ponto mais importante é o que acontece com a falha. Sem configuração extra, se a função falha em qualquer ponto do lote, todas as mensagens daquele lote voltam para a fila depois do visibility timeout. Isso reprocessa mensagens que já tinham dado certo, e por isso a função precisa ser idempotente. A saída é a resposta com falha parcial, mostrada no código de Go acima: a função devolve só os identificadores que falharam, e o restante é apagado da fila.

Para ativar essa resposta, o mapeamento precisa declarar o tipo de resposta aceito:

aws lambda create-event-source-mapping \
  --function-name pedidos-sqs \
  --event-source-arn arn:aws:sqs:sa-east-1:111122223333:pedidos.criados \
  --batch-size 10 \
  --maximum-batching-window-in-seconds 5 \
  --function-response-types ReportBatchItemFailures

Algumas regras de configuração valem ser decoradas. A recomendação da AWS é que o visibility timeout da fila seja de pelo menos seis vezes o timeout da função. O timeout da função precisa ser menor ou igual ao visibility timeout, e o Lambda valida isso na criação do mapeamento. Também é recomendado configurar a fila de mensagens mortas com maxReceiveCount de pelo menos 5, para dar algumas chances à mensagem antes de desviá-la.

lambda 04 falha lote sqs
figura 4 · lambda 04 falha lote sqs

O diagrama mostra o caminho de uma falha. Um lote de quatro mensagens chega à função. Duas passam e são apagadas. Duas falham e aparecem na resposta como batchItemFailures, então voltam para a fila e ficam visíveis de novo depois do timeout. Se elas continuam falhando até o número de tentativas configurado, o SQS as move para a fila de mensagens mortas. A cor rosa marca o caminho do erro, e o verde marca o que dá certo. Observe que a volta para a fila aparece como um elemento separado: é a mesma fila, desenhada duas vezes para não criar um ciclo no desenho.

, e

Quando uma invocação chega e não há ambiente pronto, a plataforma precisa criar um. Esse caminho é o cold start, e ele tem três partes: baixar o código, subir o runtime e executar a fase , que roda o código fora do handler. Um ambiente que já foi iniciado e ainda não foi descartado atende a próxima requisição sem repetir nada disso, é o warm start.

lambda 03 cold start warm provisioned
figura 5 · lambda 03 cold start warm provisioned

A figura tem três linhas. A primeira mostra o cold start, com as duas primeiras etapas, que são da plataforma, somando a latência que o usuário sente. A segunda mostra o ambiente reaproveitado, sem o custo de subida. A terceira mostra o provisioned concurrency, em que a fase Init acontece antes do pedido chegar, e o pedido encontra o ambiente já pronto.

Por isso o código fora do handler importa. Criar clientes de banco e ler configurações no init ou no escopo de pacote faz o trabalho pesado uma vez por ambiente. Criar o mesmo cliente dentro do handler faz esse trabalho a cada invocação, quente ou não.

A fase Init tem limite de 10 segundos para funções comuns. Para provisioned concurrency e SnapStart, o limite sobe para 130 segundos ou o timeout configurado, o que for maior. A AWS cobra a fase Init. A página de cold start diz que você é cobrado por esse tempo, o que significa que um Init lento custa dinheiro mesmo quando nenhuma requisição chega logo depois.

O provisioned concurrency mantém ambientes prontos. Ele não pode ser aplicado ao $LATEST, só a uma versão publicada ou a um alias. E ele tem custo contínuo: a AWS cobra pela concorrência provisionada e pela inicialização dos ambientes, mesmo quando eles não atendem nenhuma requisição. Use-o para APIs em que a latência de cold start é inaceitável, e não para tudo.

aws lambda publish-version --function-name pedidos-api

aws lambda create-alias \
  --function-name pedidos-api \
  --name prod \
  --function-version 3

aws lambda put-provisioned-concurrency-config \
  --function-name pedidos-api \
  --qualifier prod \
  --provisioned-concurrent-executions 20

O último comando reserva 20 ambientes para o alias prod. A saída típica traz "Status": "IN_PROGRESS" enquanto os ambientes sobem, e o status muda quando a alocação termina. Se a demanda passar de 20 execuções simultâneas, as requisições extras caem em ambientes sob demanda, com cold start normal. A variável AWS_LAMBDA_INITIALIZATION_TYPE dentro da função mostra qual dos dois tipos atendeu a invocação.

SnapStart é outra forma de reduzir o cold start, mas tem restrições. Ele aceita Java 11 e posteriores, Python 3.12 e posteriores e .NET 8 e posteriores. Go, Node.js e runtimes OS-only não são suportados, então para Go a alternativa é o provisioned concurrency.

Variáveis de ambiente e segredos

Variáveis de ambiente são o jeito de passar configuração para a função sem recompilar o código: nome de tabela, nome de bucket, nível de log. A configuração fica no próprio objeto da função, e qualquer pessoa com permissão de leitura sobre a função pode vê-la. Por isso, senha e chave de API não pertencem a esse lugar, mesmo que o Lambda as cifre em repouso. O limite total é de 4 KB para todas as variáveis somadas.

aws lambda update-function-configuration \
  --function-name pedidos-api \
  --environment "Variables={TABELA_PEDIDOS=pedidos,NIVEL_LOG=info,DATABASE_URL=postgres://proxy.exemplo:5432/loja}"

Para segredos, a forma correta é guardá-los no Secrets Manager e buscá-los uma vez, na fase Init. A função precisa da permissão secretsmanager:GetSecretValue sobre o segredo específico, e nada mais. Guardar o ARN do segredo na variável de ambiente é aceitável, porque o ARN não dá acesso a nada sem a permissão.

aws secretsmanager create-secret \
  --name pedidos/banco \
  --secret-string '{"username":"app","password":"troque-esta-senha"}'

Um ponto que costuma confundir: quando o banco usa autenticação IAM, o RDS Proxy e a função podem nem precisar de senha. A função gera um token de autenticação e o usa no lugar dela. Esse caminho é descrito na seção de RDS.

Erros comuns e como resolver

Task timed out. A função passou do timeout configurado e foi encerrada. A mensagem no log tem o formato Task timed out after 3.00 seconds, com o tempo do limite atual. Antes de subir o timeout, descubra por que a função demora: uma chamada de rede sem timeout próprio, uma consulta sem índice ou um cold start longo são os suspeitos de sempre. Subir o timeout para 15 minutos sem entender a causa só adia o problema.

AccessDenied ou “not authorized to perform”. A role de execução não tem a permissão da chamada que a função faz. O log indica a ação e o recurso. Adicione a permissão à role, restrita ao recurso certo. Se o erro vem de um gatilho, por exemplo o S3 sem conseguir invocar a função, o problema está na política de recurso da função e não na role. O remédio é o add-permission mostrado acima.

Esgotamento de conexões. Cada ambiente abre suas próprias conexões com o banco. Quando a concorrência sobe, o número de conexões sobe junto, e o banco recusa novas conexões com erros do tipo “too many connections”. Esse é o erro mais caro de diagnosticar, porque a função parece saudável até o banco travar. A solução é colocar um RDS Proxy na frente do banco e limitar o pool de cada ambiente, como no exemplo de API. A seção de RDS mostra a configuração e o diagrama correspondente.

Certificado TLS não reconhecido no Node.js. A partir do Node.js 20, o runtime gerenciado não carrega por padrão os certificados de CA adicionais, então uma conexão TLS com o RDS pode falhar com erro de certificado. A solução documentada é definir NODE_EXTRA_CA_CERTS=/var/runtime/ca-cert.pem nas variáveis de ambiente da função. Em imagens de contêiner, o bundle de certificados do RDS precisa ser incluído na imagem.

Concorrência limitada. Quando a conta atinge o limite de execuções simultâneas, as invocações novas são limitadas (throttled). Num mapeamento de fonte, isso significa que as mensagens ficam na fila e são reprocessadas depois. A reserva de concorrência por função pode proteger um serviço crítico, mas também pode limitar a própria função: se você reserva 100 e o tráfego pede 150, 50 ficam de fora.

Runtime descontinuado. O runtime go1.x já está descontinuado e bloqueado para criação de novas funções, e o provided.al2 foi descontinuado em 31 de julho de 2026. A saída é migrar para provided.al2023. A tabela de runtimes traz as datas de bloqueio de cada um, e vale conferi-la antes de copiar configurações antigas.

Exercícios

  1. Crie um mapeamento de fonte de SQS com ReportBatchItemFailures e envie três mensagens, sendo uma delas inválida. Confira no log quais foram apagadas da fila.
  2. Meça o cold start de uma função: invoque-a duas vezes com intervalo de alguns minutos e compare a linha Init Duration do log REPORT, que aparece quando a inicialização acontece.
  3. Explique com suas palavras por que um pool de 2 conexões por ambiente pode ser mais seguro que um pool de 20 quando há 100 ambientes simultâneos.

Glossário

  • Handler: a função que a plataforma chama para cada evento.
  • Init: a fase em que o ambiente é criado e o código fora do handler roda.
  • Cold start: o custo de criar um ambiente novo antes de atender a primeira invocação.
  • Warm start: a invocação que reaproveita um ambiente já iniciado.
  • Provisioned concurrency: ambientes pré-inicializados e mantidos prontos para uma versão ou alias.
  • Mapeamento de fonte: o mecanismo em que o próprio Lambda consulta uma fila e invoca a função em lotes.
  • Batch item failure: a resposta em que a função informa quais itens de um lote falharam.

Fontes

Consultado em 27/10/2026:

Referências de comandos: a própria AWS CLI instalada (aws <serviço> <comando> help), versão 2.33.18, usada para conferir nomes de parâmetros.

Não verificado nesta revisão

  • Preço do Lambda para arm64 (a página não separou claramente).
  • Número padrão de tentativas em invocação assíncrona, incluindo o disparado pelo S3.
  • Valor padrão do timeout de função (o exemplo da documentação mostra 3 segundos, mas a página de limites não o confirmou).
  • Criptografia padrão das variáveis de ambiente (a afirmação de que são cifradas em repouso não foi confirmada na página consultada).
  • Os comandos publish-version, create-alias, create-secret e put-bucket-notification-configuration não foram conferidos na ajuda local da CLI nem executados numa conta real.

↑ Voltar ao topo

Atalhos de teclado

Abrir busca
/
Alternar tema claro/escuro
t
Aula anterior na série
←
Próxima aula na série
→
Lista de atalhos
?