AWS na Prática · 06 — AWS Lambda
Computação event-driven sem servidor para gerenciar.
Computação event-driven sem servidor para gerenciar.
Sobre este módulo
Aqui o paradigma muda: nada de instâncias, nada de PaaS. Você escreve funções, define o evento que dispara, AWS executa quando preciso. Vamos substituir o consumidor de Inventory por Lambda e adicionar funções para e-mails, relatórios e mais.
Nível: Intermediário · Duração estimada: 4-5 horas
Sumário
- Lambda em 5 minutos
- Anatomia de uma função
- Triggers: como Lambda é invocada
- Modelos de execução
- Cold start e otimização
- Memory, timeout e custo
- Permissões e IAM
- Migrando o consumer Inventory
- Funções adicionais do projeto
- Layers e dependências
- Templates CloudFormation
- Exercícios de fixação
- Próximos passos
01. Lambda em 5 minutos
Lambda é o serviço mais transformador da AWS para muitas equipes. Vale entender em alto nível antes de mergulhar nos detalhes.
A ideia central
Você escreve uma função (literal: uma função em código) e a entrega para AWS. Configura um evento que dispara a função (mensagem na fila, arquivo no S3, requisição HTTP, schedule). AWS executa quando o evento chega. Você paga só pelo tempo de execução.
Não há servidor para gerenciar. Não há OS para patch. Não há scaling para configurar. AWS lida com tudo.
O que muda no modelo mental
- Não é “servidor pequeno” — é um modelo diferente, com características próprias.
- Stateless — cada execução é independente, não há estado entre chamadas.
- Tempo limitado — máximo 15 minutos por execução.
- Tamanho limitado — até 250 MB de código (com layers).
- Cold starts — primeira execução tem latência adicional.
- Concorrência gerenciada — AWS escala automaticamente até limites configurados.
Quando Lambda brilha
- Tarefas event-driven — “quando X acontecer, faça Y”.
- Tráfego intermitente — pico durante o dia, zero à noite.
- Glue code — conectar serviços AWS (S3 → DynamoDB, etc).
- Worker para filas — substituir polling de SQS.
- APIs simples — via API Gateway + Lambda.
Quando NÃO usar Lambda
- Tráfego constante e alto — pode sair mais caro que EC2/Beanstalk.
- Processamento longo — > 15 min, use Step Functions ou ECS.
- Latência crítica — cold start pode adicionar 100-1000ms.
- Workloads stateful — Lambda não é a ferramenta certa.
02. Anatomia de uma função
Toda função Lambda tem a mesma estrutura básica, independente de linguagem.
Os componentes
- Handler — função de entrada que Lambda invoca. Recebe event e context.
- Runtime — ambiente de execução: Node.js 20, Python 3.12, Java 17, Go etc.
- Configuration — memória, timeout, env vars, IAM role, VPC, layers.
- Trigger — o que invoca a função (manual, S3, SQS, EventBridge etc).
- Layers — código compartilhado entre funções (libs, dependências).
Estrutura do código (Node.js)
// index.js (apenas estrutura — você implementa a lógica)
export const handler = async (event, context) => {
console.log('Event:', JSON.stringify(event));
// Sua lógica aqui
// - Processar event
// - Acessar outros serviços (DynamoDB, SES, S3...)
// - Retornar resposta
return {
statusCode: 200,
body: JSON.stringify({ message: 'ok' })
};
};O objeto event
Cada tipo de trigger envia um event com formato diferente. Para SQS:
{
"Records": [
{
"messageId": "...",
"receiptHandle": "...",
"body": "{\"orderId\": \"ord-123\", ...}",
"attributes": { ... },
"eventSource": "aws:sqs",
"eventSourceARN": "arn:aws:sqs:...",
"awsRegion": "us-east-1"
}
]
}Records é um array — Lambda pode receber até 10 mensagens em batch (configurável). Sua função processa todas e retorna OK só se todas passaram. Se alguma falhar, configure partial batch response.
O objeto context
context = {
awsRequestId: 'unique-id-da-execução',
functionName: 'inventory-consumer',
functionVersion: '1',
invokedFunctionArn: 'arn:aws:lambda:...',
memoryLimitInMB: '256',
getRemainingTimeInMillis: () => Number,
// ... mais campos
}03. Triggers: como Lambda é invocada
Cada tipo de trigger tem características próprias — formato do event, modelo de invocação, garantias. Saber qual usar é parte importante do design.
Os principais triggers
| Trigger | Quando usar |
|---|---|
| API Gateway | APIs HTTP/REST/WebSocket |
| SQS | Consumir mensagens de fila (substitui polling) |
| SNS | Reagir a notificações pub/sub |
| S3 | Processar arquivos: upload, delete, etc |
| DynamoDB Streams | Reagir a mudanças em tabelas |
| EventBridge | Schedule (cron) ou eventos customizados |
| CloudWatch Logs | Processar/encaminhar logs |
| Kinesis | Streaming de dados |
Modelos de invocação
Síncrona
API Gateway → Lambda. Cliente espera o retorno. Erros voltam para o cliente. Sem retry automático.
Assíncrona
S3 → Lambda, SNS → Lambda. Lambda recebe, AWS retorna OK ao chamador. Lambda processa em background. Retry automático em caso de erro (até 2x). Configurar DLQ para falhas permanentes.
Polling (event source mapping)
SQS, DynamoDB Streams, Kinesis. Lambda Service faz polling internamente e invoca sua função em batches. Você não escreve loop — AWS faz por você. Esse é o modo que vamos usar com o consumer do Inventory.
Eventos parciais (partial batch)
Quando Lambda recebe um batch de 10 mensagens SQS e 1 falha, sem cuidado todas voltam para a fila — incluindo as 9 já processadas. Idempotência salva, mas há também o recurso ReportBatchItemFailures: você retorna só os IDs que falharam, e Lambda volta apenas eles para a fila.
return {
batchItemFailures: [
{ itemIdentifier: "messageId-da-que-falhou-1" },
{ itemIdentifier: "messageId-da-que-falhou-2" }
]
};04. Modelos de execução
Entender como Lambda executa em runtime é fundamental para escrever código performático.
O conceito de execution context
Quando Lambda invoca sua função, ela cria (ou reutiliza) um execution context — um container leve com runtime, dependências, variáveis. Após a execução, o contexto é mantido por algum tempo (alguns minutos), pronto para ser reutilizado.
Cold start vs warm start
| Tipo | O que acontece | Latência |
|---|---|---|
| Cold start | Container novo, código carregado, init executa | ~100-1000ms |
| Warm start | Container reutilizado, só handler executa | ~1-10ms |
Implicação prática
Código fora do handler executa uma vez por contexto (cold start). Código dentro do handler executa toda invocação. Isso muda tudo:
// FORA do handler — executa só no cold start
import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
const client = new DynamoDBClient({ region: 'us-east-1' });
// DENTRO do handler — executa toda invocação
export const handler = async (event) => {
// use o client criado no cold start
const result = await client.send(...);
return result;
};Concorrência
Lambda escala criando múltiplas instâncias do execution context em paralelo. Cada instância processa uma execução por vez.
Por padrão, você tem 1000 execuções concorrentes por região. Para limitar ou reservar capacidade, configure reserved concurrency.
05. Cold start e otimização
Cold starts são o calcanhar de Aquiles do Lambda. Em APIs com latência crítica, podem ser deal breaker. Em workers async, geralmente não importam. Mas há técnicas para reduzir.
O que afeta o cold start
- Tamanho do package — mais código, mais tempo para baixar/carregar.
- Runtime — Node.js e Python são rápidos; Java pode levar segundos.
- Memória — mais memória = mais CPU = startup mais rápido.
- VPC — funções em VPC tinham cold start maior (resolvido em 2019).
- Layers — layers grandes adicionam tempo de cold start.
Estratégias de mitigação
1. Reduzir o package
Não inclua o que não é necessário. Use bundlers como esbuild para tree-shaking. Considere ESM em vez de CommonJS.
2. Aumentar memória
Função com 512 MB tem cerca de 2x mais CPU que com 256 MB. Cold start cai proporcionalmente. Custo aumenta — mas se você consegue reduzir tempo de execução pela metade, o custo total pode diminuir.
3. Lazy loading
Em vez de importar todas as deps no topo, importe só quando precisar. Útil para deps grandes usadas em paths raros.
4. Provisioned Concurrency
Você paga para manter N instâncias sempre warm. Elimina cold starts para esse N. Útil em APIs críticas com latência baixa requerida. Custa dinheiro mesmo sem invocações.
06. Memory, timeout e custo
As três configurações que mais afetam custo e performance.
Memória (e CPU implícita)
Você escolhe entre 128 MB e 10.240 MB. CPU é proporcional: 1.769 MB = 1 vCPU completa. Mais memória = mais CPU = execução mais rápida.
Não pense em memória só pelo que sua função consome — pense também em performance. Uma função CPU-bound com 128 MB pode ser 3x mais cara que com 1024 MB porque demora 3x mais.
Timeout
Tempo máximo que sua função pode rodar antes de ser interrompida. Default: 3 segundos. Máximo: 15 minutos. Configure conforme o caso de uso real.
| Caso de uso | Timeout sugerido |
|---|---|
| API simples (consulta DB) | 3-5s |
| Processar evento SQS | 30s-1min |
| Conversão de imagem | 1-3min |
| ETL pequeno | 5-15min |
Configurar timeout muito alto não custa — mas aumenta o tempo que uma função problemática fica rodando antes de morrer. Configure o necessário, não o máximo.
Como Lambda cobra
Você paga por:
- Requests: US$ 0.20 por milhão.
- Compute time: US$ 0.0000166667 por GB-segundo (memória * tempo). Exemplo: função com 256 MB que executa 100ms, invocada 1 milhão de vezes:
Compute = 0.25 GB * 0.1s * 1.000.000 = 25.000 GB-segundos
Custo compute = 25.000 * US$ 0.0000166667 = US$ 0.42
Custo requests = 1M * US$ 0.20/M = US$ 0.20
Total = US$ 0.62
Se for <= 1M req e <= 400.000 GB-s no mês: GRÁTIS (Free Tier)
07. Permissões e IAM
Como Beanstalk, Lambda assume uma role IAM e herda suas permissões. Cada função, sua role, com least privilege.
Execution role
Toda Lambda precisa de uma execution role — a IAM role que ela assume durante a execução. Ela define o que sua função pode fazer:
- AWSLambdaBasicExecutionRole — gravar logs no CloudWatch (sempre incluir).
- AWSLambdaSQSQueueExecutionRole — para funções consumidoras de SQS.
- AWSLambdaDynamoDBExecutionRole — para DynamoDB Streams.
- Custom policies — permissões específicas para sua tabela, bucket, etc.
Resource-based policies
Algumas integrações usam policy na própria Lambda (em vez de no trigger). Por exemplo: para S3 invocar sua Lambda, S3 precisa de permissão registrada na função.
CloudFormation cuida disso automaticamente quando você define um trigger via Events em AWS::Lambda::Function. Manualmente, você usa aws lambda add-permission.
Padrão para o consumer Inventory
InventoryConsumerRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service: lambda.amazonaws.com
Action: sts:AssumeRole
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AWSLambdaSQSQueueExecutionRole
Policies:
- PolicyName: dynamodb-inventory-write
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- dynamodb:GetItem
- dynamodb:UpdateItem
Resource: !ImportValue inventory-db-TableArn08. Migrando o consumer Inventory
No Módulo 05, o Inventory Service rodava no Beanstalk fazendo polling no SQS. Vamos refatorar: o serviço HTTP continua no Beanstalk, mas o consumer vira uma Lambda separada.
A nova arquitetura
- Não é “servidor pequeno” — é um modelo diferente, com características próprias.
- Stateless — cada execução é independente, não há estado entre chamadas.
- Tempo limitado — máximo 15 minutos por execução.
- Tamanho limitado — até 250 MB de código (com layers).
- Cold starts — primeira execução tem latência adicional.
- Concorrência gerenciada — AWS escala automaticamente até limites configurados.
Estrutura da função
// process-order-event/index.js
// Estrutura — você escreve a lógica
import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
import { DynamoDBDocumentClient, UpdateCommand } from
'@aws-sdk/lib-dynamodb';
const ddb = DynamoDBDocumentClient.from(
new DynamoDBClient({ region: process.env.AWS_REGION })
);
export const handler = async (event) => {
const failures = [];
for (const record of event.Records) {
try {
const eventBody = JSON.parse(record.body);
// Lógica idempotente (verificar se evento já processado,
// depois decrementar estoque)
await processOrderEvent(eventBody);
} catch (err) {
console.error('Falha:', err);
failures.push({ itemIdentifier: record.messageId });
}
}
return { batchItemFailures: failures };
};Configurações importantes
- Memory: 256 MB (suficiente para a operação).
- Timeout: 30s (margem para o batch).
- Batch size: 10 (máximo SQS, padrão).
- Maximum batching window: 5s (latência aceitável vs custo).
- Reserved concurrency: 10 (proteger DynamoDB de picos).
09. Funções adicionais do projeto
Vamos aproveitar Lambda para implementar funcionalidades que sempre faltam em sistemas reais: notificações, relatórios, processamento de imagens.
send-order-confirmation-email
- Trigger: SQS de e-mails (inscrita no topic order.created).
- Faz: envia e-mail via SES com confirmação do pedido.
- Memory: 256 MB. Timeout: 10s.
- Permissões: ses:SendEmail.
daily-sales-report
- Trigger: EventBridge schedule (cron diário às 6:00).
- Faz: agrega vendas do dia anterior, gera CSV, salva no S3.
- Memory: 1024 MB. Timeout: 5min.
- Permissões: dynamodb:Query (Orders), s3:PutObject.
resize-product-image
- Trigger: S3 upload no bucket products-original.
- Faz: redimensiona em 3 tamanhos (thumb, medium, large) e salva em products-resized.
- Memory: 1024 MB. Timeout: 1min.
- Layer: sharp (lib de processamento de imagens).
payment-retry-handler
- Trigger: SNS topic payment.failed.
- Faz: agenda nova tentativa em SQS delay queue, com backoff exponencial.
- Memory: 128 MB. Timeout: 5s.
10. Layers e dependências
Layers são pacotes de código compartilhados entre funções. Úteis para deps grandes, libs comuns, runtime customizado.
O problema sem layers
Cada função tem seu próprio package zip. Se 5 funções usam sharp (40 MB), você tem 200 MB duplicados. Pior: subir cada função fica lento.
Como funcionam
Você cria um zip com nodejs/node_modules/<libs> e publica como layer. Cada função pode anexar até 5 layers. As libs aparecem como se estivessem no node_modules da função.
Limitações
- Tamanho total (função + layers): 250 MB descompactados.
- Máximo 5 layers por função.
- Layers são por região — você publica em cada região onde usa.
- Versionadas — toda atualização cria nova versão; funções fixam em uma versão.
Quando usar
- Libs grandes compartilhadas entre funções (sharp, pandas, etc).
- Código comum do seu projeto (tipos, utils, clients).
- Runtime customizado (ex: Rust, Bash).
- SDKs específicos não incluídos no runtime padrão.
11. Templates CloudFormation
Lambda em CloudFormation tem várias particularidades. Vamos cobrir o essencial.
Função Lambda básica
InventoryConsumerFunction:
Type: AWS::Lambda::Function
Properties:
FunctionName: inventory-consumer
Runtime: nodejs20.x
Handler: index.handler
MemorySize: 256
Timeout: 30
Role: !GetAtt InventoryConsumerRole.Arn
Code:
S3Bucket: !Ref CodeBucket
S3Key: lambdas/inventory-consumer-v1.zip
Environment:
Variables:
AWS_REGION_NAME: !Ref AWS::Region
INVENTORY_TABLE: !ImportValue inventory-db-TableName
ReservedConcurrentExecutions: 10
Tags:
- Key: Project
Value: order-systemEvent source mapping (SQS → Lambda)
InventorySQSEventSource:
Type: AWS::Lambda::EventSourceMapping
Properties:
EventSourceArn: !ImportValue messaging-InventoryQueueArn
FunctionName: !Ref InventoryConsumerFunction
BatchSize: 10
MaximumBatchingWindowInSeconds: 5
FunctionResponseTypes:
- ReportBatchItemFailuresSchedule (EventBridge)
DailyReportSchedule:
Type: AWS::Events::Rule
Properties:
ScheduleExpression: cron(0 6 * * ? *) # 6h UTC todo dia
Targets:
- Arn: !GetAtt DailyReportFunction.Arn
Id: DailyReport
DailyReportPermission:
Type: AWS::Lambda::Permission
Properties:
FunctionName: !Ref DailyReportFunction
Action: lambda:InvokeFunction
Principal: events.amazonaws.com
SourceArn: !GetAtt DailyReportSchedule.ArnTrigger por S3
ProductsBucket:
Type: AWS::S3::Bucket
Properties:
BucketName: products-original-${AWS::AccountId}
NotificationConfiguration:
LambdaConfigurations:
- Event: s3:ObjectCreated:*
Function: !GetAtt ResizeImageFunction.Arn12. Exercícios de fixação
Implemente e suba o Inventory Consumer
Refatore o consumer do Módulo 05 como função Lambda inventory-consumer. Suba o zip para S3 e use template CloudFormation. Configure SQS event source. Teste: criar pedido em Orders deve disparar a Lambda e decrementar estoque.
Desligue o consumer Beanstalk
Depois que a Lambda estiver funcionando, termine o environment Beanstalk que rodava o consumer. Confirme que: (a) o environment foi removido; (b) novos pedidos continuam sendo processados (agora pela Lambda); (c) a fila não está acumulando.
Implemente send-order-confirmation-email
Crie a função, configure SES para enviar do seu e-mail (precisa verificar domínio/e-mail no SES sandbox primeiro). Crie subscription no topic order-events. Confirme: ao criar pedido, você recebe e-mail.
Implemente daily-sales-report
Crie a função com EventBridge schedule (use schedule de teste, ex: a cada 5 minutos, e depois mude para 1x/dia). A função consulta Orders do dia via GSI by-status, gera CSV em memória, faz PutObject no S3. Verifique que o arquivo aparece no bucket após o cron rodar.
Compare custos: antes vs depois
Reflexão escrita: descreva os custos do consumer Inventory antes (rodando 24/7 em t2.micro) e depois (Lambda invocada apenas quando há mensagens). Considere custos de EC2, custos de Lambda (free tier!), custos de manutenção (patching, monitoring). Em que volume Beanstalk volta a ser mais barato?
13. Próximos passos
Com Lambda no jogo, seu sistema combina três modelos de computação: PaaS (Beanstalk) para serviços HTTP, FaaS (Lambda) para tarefas event-driven, IaC (CloudFormation) amarrando tudo. Falta uma peça final: saber o que está acontecendo em produção.
O que você aprendeu
- Modelo de computação serverless.
- Anatomia de uma função: handler, runtime, configuration.
- Triggers principais: API Gateway, SQS, SNS, S3, EventBridge.
- Modelos de invocação: síncrona, assíncrona, polling.
- Cold start, warm start, e estratégias de otimização.
- Como Lambda cobra e como pensar em custo.
- Permissões IAM e princípio de least privilege.
- Migração do consumer Inventory para Lambda.
- Funções adicionais: emails, reports, image resize.
- Layers para deps compartilhadas.
- Templates CloudFormation completos para Lambda.
O que vem no Módulo 07
Observabilidade e finalização. Agora que tudo está rodando, vamos garantir que você sabe o que está acontecendo: CloudWatch Logs, métricas, alarmes, X-Ray para distributed tracing. E vamos encerrar o curso com discussão sobre próximos passos: como evoluir o sistema, o que estudar a seguir.
Sem servidor não é sem responsabilidade. Boa jornada — Módulo 07 a seguir.