AWS na Prática · 06 — AWS Lambda

Computação event-driven sem servidor para gerenciar.

17 min de leitura

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

  1. Lambda em 5 minutos
  2. Anatomia de uma função
  3. Triggers: como Lambda é invocada
  4. Modelos de execução
  5. Cold start e otimização
  6. Memory, timeout e custo
  7. Permissões e IAM
  8. Migrando o consumer Inventory
  9. Funções adicionais do projeto
  10. Layers e dependências
  11. Templates CloudFormation
  12. Exercícios de fixação
  13. 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

TriggerQuando usar
API GatewayAPIs HTTP/REST/WebSocket
SQSConsumir mensagens de fila (substitui polling)
SNSReagir a notificações pub/sub
S3Processar arquivos: upload, delete, etc
DynamoDB StreamsReagir a mudanças em tabelas
EventBridgeSchedule (cron) ou eventos customizados
CloudWatch LogsProcessar/encaminhar logs
KinesisStreaming 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

TipoO que aconteceLatência
Cold startContainer novo, código carregado, init executa~100-1000ms
Warm startContainer 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 usoTimeout sugerido
API simples (consulta DB)3-5s
Processar evento SQS30s-1min
Conversão de imagem1-3min
ETL pequeno5-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-TableArn

08. 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-system

Event source mapping (SQS → Lambda)

InventorySQSEventSource:
  Type: AWS::Lambda::EventSourceMapping
  Properties:
    EventSourceArn: !ImportValue messaging-InventoryQueueArn
    FunctionName: !Ref InventoryConsumerFunction
    BatchSize: 10
    MaximumBatchingWindowInSeconds: 5
    FunctionResponseTypes:
      - ReportBatchItemFailures

Schedule (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.Arn

Trigger por S3

ProductsBucket:
  Type: AWS::S3::Bucket
  Properties:
    BucketName: products-original-${AWS::AccountId}
    NotificationConfiguration:
      LambdaConfigurations:
        - Event: s3:ObjectCreated:*
          Function: !GetAtt ResizeImageFunction.Arn

12. Exercícios de fixação

Exercício
01

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.

Exercício
02

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.

Exercício
03

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.

Exercício
04

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.

Exercício
05

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.

Posts relacionados