05 - Serverless (Funções como Serviço)
video: https://youtu.be/... no frontmatter da nota.Serverless (Funções como Serviço)
Serverless é o modelo em que o provedor de nuvem administra servidores, escalonamento e capacidade ociosa, e o time paga por execução real em vez de máquina alocada. O termo cobre duas famílias complementares: FaaS, funções como serviço, em que código é acionado por evento e executa em ambiente efêmero, e BaaS, backend como serviço, em que capacidades inteiras, como autenticação, armazenamento e filas, são consumidas prontas. A AWS Lambda é a referência histórica e comercial do modelo, com Azure Functions e Google Cloud Functions seguindo a mesma forma. Em produção, serverless brilha em cargas esparsas e orientadas a eventos, e cobra seu preço em restrições de tempo, memória e portabilidade que precisam ser assumidas no desenho, e não descobertas no incidente.
Conceito: FaaS e BaaS
No FaaS, a unidade de implantação é uma função: você envia código, declara gatilhos e o provedor instancia ambientes, escala até milhares de execuções paralelas e devolve a zero quando o tráfego cessa. No BaaS, você usa serviços gerenciados por API, como Cognito para identidade, S3 para objetos e DynamoDB para dados, e seu código próprio encolhe para o que é genuinamente específico do negócio. Sistemas serverless completos combinam os dois: funções orquestram regras, e serviços gerenciados sustentam o resto.
A promessa de escalar até zero é também a definição do perfil ideal de carga: o modelo transfere para o provedor a preocupação com capacidade ociosa e picos, e cobra por uso real. A contrapartida invisível no slide é que esse elasticismo depende de funções stateless, curtas e desenhadas para reentrância, e que qualquer componente de estado persistente precisa ser externo e com capacidade própria planejada.
| Aspecto | FaaS | BaaS |
|---|---|---|
| Unidade entregue | Função acionada por evento | Serviço gerenciado pronto |
| Código próprio | Lógica de negócio da função | Configuração e integração |
| Escala | Automática por invocação, até zero | Automática, gerida pelo serviço |
| Exemplos | AWS Lambda, Azure Functions | Cognito, DynamoDB, S3, Firebase |
| Risco dominante | Cold start e limites de execução | Acoplamento ao provedor |
Funcionamento Interno no AWS Lambda
A Lambda executa cada função em ambiente isolado, hoje baseado em Firecracker, microVM leve mantida pela AWS. O ciclo tem duas fases: init, em que o código é baixado, o runtime é iniciado e o código de inicialização roda uma vez, e invoke, em que a requisição é processada; o ambiente pode ser reaproveitado para chamadas seguintes, o que explica por que objetos globais sobrevivem entre invocações. A concorrência é o número de ambientes simultâneos, controlável com reserved concurrency para proteger sistemas de apoio e com provisioned concurrency para eliminar latência de preparo. Os limites de formato são parte do contrato: até 15 minutos de execução, memória de 128 MB a 10 GB com CPU proporcional, e teto de payload nos gatilhos síncronos e assíncronos.
# Função Lambda acionada por fila: padrão típico de processamento assíncrono
Resources:
ProcessarPedidoFn:
Type: AWS::Serverless::Function
Properties:
Runtime: python3.12
MemorySize: 512 # memória dimensiona também vCPU e rede
Timeout: 60 # abaixo do teto de 15 minutos
Tracing: Active # correlação de trace por invocação
Events:
Fila:
Type: SQS
Properties:
Queue: !GetAtt PedidosQueue.Arn
BatchSize: 10 # agrupa eventos por execução
MaximumBatchingWindowInSeconds: 5 # espera para formar lote
PedidosQueue:
Type: AWS::SQS::Queue
Properties:
RedrivePolicy: # mensagens venenosas vão para a DLQ
deadLetterTargetArn: !GetAtt PedidosDLQ.Arn
maxReceiveCount: 5
PedidosDLQ:
Type: AWS::SQS::Queue
Modelos de invocação e seus efeitos práticos:
- Síncrona: API Gateway ou ALB chama e espera a resposta; latência e erro voltam direto ao cliente, e o teto de payload se aplica.
- Assíncrona: S3, EventBridge ou SNS disparam, e a plataforma enfileira com retentativas configuráveis; falhas viram eventos em fila de destino.
- Streams e filas: SQS, Kinesis e DynamoDB Streams entregam em lotes, e o processamento parcial exige marcadores de progresso para não duplicar efeito.
Os limites de plataforma moldam o desenho antes de qualquer linha de código:
- 15 minutos por execução: jobs longos, como fechamento contábil pesado, não cabem; dividir em passos com Step Functions ou mover para containers.
- Payload síncrono limitado: arquivos e imagens grandes não viajam no corpo da requisição; sobem em S3 e viajam como referência.
- Memória de 128 MB a 10 GB: a alocação dimensiona CPU e rede junto, e tarefas de processamento intenso precisam de medição real.
- Concorrência compartilhada da conta: um burst de uma função pode esgotar a cota e bloquear vizinhas; reserved concurrency protege o que é crítico.
- Ambiente efêmero: arquivos escritos em /tmp somem entre invocações, e nada de sessão em memória sobrevive a escala.
Cold Start e Estado Externo
Cold start é a latência extra da fase init quando um ambiente novo precisa nascer: acontece no primeiro acesso após deploy, em picos que exigem escala nova e em funções ociosas cujo ambiente foi reciclado. O impacto aparece no p99, e não no p50: a maioria das invocações voa, e uma minoria penaliza. Mitigações existem e têm custo: provisioned concurrency mantém ambientes prontos cobrando 24 horas por dia, runtimes compilados e enxutos reduzem a fase init, e recursos como SnapStart da AWS aceleram linguagens de máquina virtual. Funções com VPC e conexões pesadas pioram o cenário, e precisam de desenho consciente.
A leitura operacional correta é tratar cold start como variável de capacidade, e não como defeito: mede-se a distribuição por runtime, identifica-se quais funções têm requisito de latência real, e paga-se provisão apenas para elas. Aplicar provisioned concurrency em toda função é a forma mais rápida de perder a vantagem econômica do modelo.
Como funções são efêmeras, todo estado mora fora: memória local e disco temporário somem entre invocações, e qualquer dado persistente vai para S3, DynamoDB, Redis ou banco. O padrão que mais pega desprevenidos é o pool de conexões: cada ambiente simultâneo abre suas conexões com o banco, e um pico de concorrência esgota o limite do PostgreSQL em segundos. A resposta é intermediar com proxy de conexão, como RDS Proxy, ou usar APIs HTTP sobre o banco, ou manter a concorrência limitada deliberadamente.
| Causa de latência | Sintoma observado | Mitigação |
|---|---|---|
| Cold start de ambiente | p99 alto em tráfego esparso | Provisioned concurrency, runtime leve, SnapStart |
| Pool de conexões | Esgotamento de conexões no banco | RDS Proxy, limitação de concorrência |
| Cadeia síncrona de funções | Latência soma de ponta a ponta | Orquestração assíncrona com Step Functions |
| Payload grande em gatilho síncrono | Erros de limite e custo de rede | Armazenar em S3 e passar referência |
Cobrança por Invocação
A conta do FaaS soma número de invocações e tempo de execução ponderado pela memória alocada, em GB-segundo, com escalas gratuitas generosas para cargas pequenas. Para tráfego esparso, sazonal ou de madrugada, a economia é real, porque não há capacidade ociosa paga. Para carga constante e alta, funções podem sair mais caras que containers dimensionados, e a comparação honesta exige simular o perfil de tráfego real. Detalhes que pesam no orçamento: funções conversadoras em cadeia multiplicam invocações, logs e tracing têm custo próprio, e excesso de memória alocada multiplica o GB-segundo sem ganho correspondente.
Padrões de custo frequentes em produção:
- Função de uso geral gigante: tudo em uma função significa pagar memória máxima por tarefa simples; fragmentar por perfil economiza.
- Lotes mal dimensionados: batch pequeno demais multiplica invocações; grande demais estoura timeout; o ajuste é medido, não adivinhado.
- Chamadas entre regiões e zonas: tráfego cruzado soma na conta e na latência; desenho regional evita desperdício.
Padrões Event-Driven e Orquestração
Serverless e arquitetura orientada a eventos nasceram juntas: os gatilhos nativos são eventos, e o desenho natural é de funções pequenas reagindo a fatos. Fan-out com SNS distribuindo para várias filas, buffer com SQS protegendo o processamento, e EventBridge roteando eventos entre domínios compõem o repertório básico. Para fluxos com passos, retentativa e decisão, Step Functions assume a orquestração com estado durável, retentativas declarativas e observabilidade do fluxo inteiro, em vez de construir máquina de estados dentro de uma função. Regra de ouro do desenho: funções curtas e de um propósito, orquestração externa para o fluxo, e idempotência no consumidor porque a entrega é at-least-once.
Cenários em que a escolha costuma acertar:
- Cargas esporádicas e sazonais: relatórios noturnos, integrações de madrugada e picos de campanha pagam por uso real, sem servidor parado.
- Webhooks e integrações externas: receber evento de parceiro, validar e encaminhar é o caso de uso nativo do modelo.
- Processamento de arquivos e mídia: upload em S3 disparando transformação, thumbnail ou extração de dados, com escala proporcional ao volume.
- Times sem equipe de plataforma: o provedor assume patching, capacidade e escalonamento, e o time foca na lógica de negócio.
Vendor Lock-in
O acoplamento ao provedor é estrutural em serverless: os gatilhos, as permissões, os limites e os serviços de apoio são específicos, e migrar significa reescrever integração. A pergunta útil não é como eliminar o lock-in, e sim onde ele é aceitável: para infraestrutura de commodity, como fila e armazenamento, o custo de troca é aceitável diante do ganho; para regra de negócio central, o acoplamento é risco real. A mitigação clássica é hexagonal no pequeno: a função é adaptador, a regra mora em módulo puro testável sem nuvem, e a infraestrutura fica declarada em IaC como Terraform ou CDK, para que o ambiente inteiro seja reproduzível. Formatos abertos como CloudEvents reduzem atrito na camada de evento, e runtimes portáveis evitam prisão de linguagem, mas a promessa de portabilidade total raramente sobe o teste da migração real.
Uma tática de mitigação incremental que funciona em produção: concentrar a dependência específica do provedor em adaptadores finos dentro de cada função, e manter bibliotecas de domínio compartilhadas independentes de SDK. Quando a migração acontecer, o esforço se limita a reescrever o anel externo de cada função, e o núcleo testado continua idêntico.
Erros Comuns e Boas Práticas
| Sintoma em produção | Causa provável | Mitigação |
|---|---|---|
| p99 alto em função "rápida" | Cold start em tráfego esparso | Provisioned concurrency ou runtime enxuto |
| Banco cai em pico de tráfego | Pool por ambiente sem limite | RDS Proxy e concorrência reservada |
| Mensagens perdidas em erro de validação | Sem DLQ e sem retentativa configurada | DLQ por origem e alerta de profundidade |
| Timeout em job de fechamento | Tarefa longa dentro da Lambda | Extrair para Fargate ou Step Functions |
| Conta surpresa no fim do mês | Invocações e memória sem revisão | Alarme de custo e perfil de memória medido |
Boas práticas consolidadas para o dia a dia:
- Uma função, um propósito: funções curtas com responsabilidade única simplificam escala, permissão e análise de custo.
- Idempotência desde o primeiro desenho: entrega duplicada é propriedade do modelo, não exceção.
- Estado sempre fora do ambiente: qualquer dado em memória ou disco local é cache descartável, nunca fonte de verdade.
- Toda fila tem DLQ e alerta: mensagem venenosa silenciosa é incidente que só aparece no relato de cliente.
- IaC obrigatório: funções criadas no console são dívida instantânea, sem reprodução nem revisão.
- Permissão mínima por função: papel dedicado com escopo estreito evita que um comprometimento alcance tudo.
- Meça memória real e ajuste: perfil de uso com ferramenta própria costuma reduzir custo sem tocar em código.
- Trate configuração fora do código: parâmetros em Parameter Manager ou Secrets Manager, nunca em variável commitada.
- Escolha runtime pelo perfil da função: runtime enxuto reduz init; linguagem compilada ajuda nas funções de caminho quente.