Lab 30 — Os limites do serverless, medidos
O problema, e a empresa que o tem
A Cadência migrou toda a sua API para Lambda no laboratório anterior — cota por cliente, banco de dados, tudo em funções que escalam sozinhas e não cobram hora ociosa. O time gostou tanto do resultado que decidiu "levar o Lambda até o fim": o fechamento mensal, que consolida os pedidos das 30 lojas num relatório único para o financeiro, também vira função.
O problema aparece no primeiro mês em que alguém mede em vez de supor. O job leva de 20 a 35 minutos — a variação depende de quantas lojas fecharam o mês em atraso — e a primeira execução em produção falha com Task timed out after 900.00 seconds. A resposta de menor esforço, escrita numa tarde, é fazer a função invocar a si mesma antes do relógio acabar, salvando o progresso numa tabela. Funciona no teste. Falha, sem aviso, na virada do trimestre.
Este laboratório não escolhe entre "Lambda" e "não Lambda" por gosto. Ele mede os cinco limites que decidem essa escolha — e mostra que contornar só o mais óbvio dos cinco (o timeout) não resolve o problema: move o defeito para os outros quatro.
O que este laboratório NÃO é
Não é um tutorial de "como montar um PDF em C#". A geração do relatório em si é tratada como caixa-preta que leva 20 a 35 minutos — o que importa aqui é ONDE essa caixa-preta pode rodar sem bater teto, não como ela é implementada por dentro. Se você precisa de orquestração de múltiplas ferramentas de IA sobre o mesmo dado, o assunto é outro — este módulo é sobre limite de serviço, não sobre geração de conteúdo.
O que você vai conseguir fazer
Objetivos verificáveis: cada um se prova com um número na seção de implantação, não com a sensação de "acho que não cabe".
- Citar de memória os cinco limites que decidem se um job cabe em Lambda, com o valor atual de cada um.
- Medir a duração real de um job fora de qualquer teto de serviço, antes de escolher plataforma.
- Explicar por que auto-invocação com checkpoint troca uma falha clara por três silenciosas.
- Distinguir o teto de execução Standard (1 ano) do Express (5 minutos) do Step Functions, e escolher o certo.
- Justificar por que o payload entre estados do Step Functions atravessa por referência (URI), não por valor.
- Configurar uma task Fargate com CPU, memória e armazenamento efêmero compatíveis com o job real.
- Provar, com número, que o teto de concorrência do Lambda é da CONTA, não da função isolada.
- Aplicar a regra de decisão: quando um job volta para contêiner, e por qual dos cinco limites.
O que a certificação cobra disto
| Conceito | Certificação | Como aparece aqui | O que dominar |
|---|---|---|---|
| Timeout de função como cota rígida | DVA-C02, SAA-C03 | 900 s, sem pedido de aumento possível | diferenciar cota rígida (timeout) de cota ajustável (memória, concorrência) |
| Payload síncrono vs assíncrono | DVA-C02 | 6 MB (síncrono, cada sentido) contra 1 MB (assíncrono) | por que um relatório de 45 MB reprova as duas formas de invocação |
| Armazenamento efêmero /tmp | DVA-C02, SAA-C03 | 512 MB padrão, até 10.240 MB configurável | quando o dataset intermediário aproxima o teto, mesmo no máximo |
| Concorrência: conta vs função | DVA-C02, SAA-C03 | 1.000 de teto padrão, compartilhado por TODA a conta na região | que reserved concurrency reparte o teto existente, não cria capacidade nova |
| Pacote de deployment: zip vs imagem | DVA-C02 | 50 MB (zip via API/console), 250 MB descompactado, 10 GB de imagem de contêiner | quando uma dependência pesada obriga a trocar de formato de empacotamento |
| Step Functions Standard vs Express | DVA-C02, SAA-C03, SAP-C02 | Standard (1 ano) escolhido de propósito contra Express (5 min) | que o tipo não muda depois de criado — a escolha errada exige recriar |
| Payload por estado do Step Functions | DVA-C02, SAP-C02 | 256 KiB de entrada ou saída por estado, tarefa ou execução | por que a URI atravessa e o dado, não |
| Tamanho de task Fargate | DVA-C02, SAA-C03 | combinações de CPU/memória até 32 vCPU e 244 GB | que nem todo par de CPU e memória é aceito — precisa vir da tabela oficial |
Onde isto costuma ser cobrado errado
A pergunta clássica dá um job de duração incerta e pede a arquitetura "mais barata". A resposta errada mais comum é Step Functions Express, porque o nome sugere "mais leve" e o preço por transição é menor. Express tem teto de execução de 5 minutos — metade de qualquer job que já não coubesse num Lambda de 900 s. O critério certo não é preço por transição: é se a duração do job cabe no tipo escolhido.
Requisitos, e como cada um muda o desenho
Cada linha abaixo é um limite medido, não uma preferência de estilo. A coluna da direita é onde a medição vira decisão de arquitetura.
| Requisito | Valor medido | O que ele decide no desenho |
|---|---|---|
| Duração real do job | 20 a 35 min (23 min 40 s no dado de referência) | reprova Lambda sozinho: ultrapassa o teto rígido de 900 s por um fator de até 2,3 |
| Tamanho do relatório final | até 45 MB | reprova resposta síncrona (6 MB) e assíncrona (1 MB) do Lambda; obriga passagem por S3 |
| Tamanho do dataset intermediário | 10 a 15 GB antes de compactar | perto do teto de 10.240 MB do /tmp mesmo configurado no máximo; Fargate com 30 GiB de armazenamento efêmero sobra folga confortável |
| Execuções simultâneas no pico | até 3, em virada de trimestre | não é grande o bastante para estourar 1.000 de concorrência do Lambda sozinho — mas a CADEIA de auto-invocação multiplica invocações por execução, e isso muda a conta |
| Dependência de renderização pesada | motor de PDF com fontes embutidas, ~300 MB de imagem | passa dos 50 MB do zip e dos 250 MB descompactado; exige empacotamento como imagem de contêiner — que tanto Lambda (até 10 GB) quanto Fargate aceitam nativamente |
| Ninguém aguarda resposta em navegador | execução é agendada, não solicitada por usuário | dispensa API Gateway (que teria seu próprio teto de 29 s) e dispensa Express Workflow, que só valeria a pena se a resposta precisasse ser síncrona e rápida |
Arquitetura mínima: o job forçado em Lambda
Este é o desenho que a Cadência publicou na correria, e ele é legítimo como ponto de partida: sobe de verdade, sem recurso novo na fatura. O laboratório começa medindo onde ele quebra — porque um número torna o defeito discutível, e "não coube" sozinho não.
- → cron mensal (StartExecution equivalente)
- → invocação assíncrona antes do timeout, "continue de onde parei"
- → lê e grava o ponteiro de progresso por loja
- → grava a fatia do relatório já processada
- → Duration e Errors de cada invocação da cadeia
- → confere manualmente se a fatia final terminou de ser gravada
- Integração de apps
- Compute
- Banco de dados
- Armazenamento
- Gestão e governança
- Fora da AWS
Este desenho publica, e por isso ele sobrevive: nenhuma peça nova, só Lambda chamando a si mesmo. O defeito não é o timeout batendo — é que contorná-lo troca uma falha clara (erro de timeout) por três silenciosas (duplicidade, concorrência roubada, ausência de aviso). Percorra os passos na ordem: cada um mostra qual teto a gambiarra atinge depois do primeiro.
- O agendamento não conhece a duração do job. O EventBridge Scheduler só dispara. Nada nele impede agendar um trabalho que ultrapassa o teto do alvo — essa checagem não existe em nenhuma camada anterior à primeira invocação, e é por isso que o defeito só aparece em produção, no primeiro mês em que o volume de lojas cresce o bastante.
- 900 segundos é teto endurecido, não sugestão. A primeira invocação já nasce contra um limite que o Service Quotas não permite pedir para aumentar — ao contrário de memória e de concorrência, o timeout de função do Lambda é cota rígida, fixada em 900 segundos (15 minutos). Não existe formulário para isso.
- O checkpoint muda a NATUREZA da falha, não a resolve. Guardar o ponteiro de progresso destrava o job, mas troca "timeout claro" por "idempotência que ninguém projetou": se a invocação seguinte falhar antes de gravar o novo ponteiro e for reexecutada, a mesma loja é processada duas vezes, e nada no desenho detecta isso.
- As fatias escondem o verdadeiro risco: nada garante a última. Cada invocação grava seu pedaço do relatório em separado. Se a cadeia parar na invocação 14 de 15 — por erro transiente, por throttling, por deploy no meio do mês — o bucket tem catorze fatias e nenhuma delas é "o relatório", e nada avisa que falta uma.
- O sinal de que algo está errado mora no painel, não no código. Sem instrumentação extra, a única forma de saber que a cadeia está perto do limite é olhar `Duration` aproximando-se de 900000 ms em cada invocação — o código não lança exceção nenhuma até o momento em que o timeout realmente ocorre.
- Não há aviso: alguém tem de ir procurar. Diferente da arquitetura de produção deste laboratório, nada aqui publica "terminou" ou "falhou". O time financeiro descobre o estado do fechamento contando objetos num bucket — e só percebe um problema quando alguém precisa do número e ele não está lá.
O primeiro sintoma em produção é o mais fácil de diagnosticar. Os seguintes, não.
# CloudWatch Logs, primeira execucao em producao, mes de agosto de 2026:
REPORT RequestId: 8f2c1a... Duration: 900002.14 ms Billed Duration: 900000 ms Memory Size: 1024 MB Max Memory Used: 743 MB
Task timed out after 900.00 seconds
# A "correcao" de uma tarde: a funcao passa a se auto-invocar de forma assincrona
# aos 800s, gravando o progresso numa tabela. O log seguinte parece bem-sucedido:
REPORT RequestId: 3a91f0... Duration: 803441.02 ms Billed Duration: 803500 ms
(async invoke) -> nova invocacao iniciada, checkpoint = loja 17 de 30O sintoma que a gambiarra esconde
Depois da auto-invocação, o CloudWatch para de mostrar erro de timeout — e é exatamente isso que engana. A ausência de erro não significa ausência de defeito: significa que o defeito migrou para um lugar sem alarme configurado. Duplicidade de loja processada duas vezes, cadeia que para no meio sem terminar e concorrência roubada de outra função não geram nenhum log de erro por conta própria.
Arquitetura para produção
Cada peça nova abaixo rastreia a uma linha da tabela de requisitos. Se você não conseguir apontar qual medição a exige, a peça é adorno — e este desenho não tem nenhuma.
- → inicia a execução mensal (StartExecution)
- → passa a URI do dataset, não os bytes
- → RunTask.sync — aguarda o código de saída do contêiner
- → lê o dataset bruto pela URI recebida
- → grava o PDF consolidado do fechamento
- → recebe de volta só a URI do resultado
- → histórico de execução e duração de cada estado
- → publica sucesso ou falha (Retry/Catch declarativos)
- → e-mail ou Slack com o link do relatório pronto
- Integração de apps
- Compute
- Armazenamento
- Gestão e governança
- Fora da AWS
A pergunta deixa de ser "como caber no Lambda" e passa a ser "o que cada peça não pode fazer". Cada peça nova abaixo existe porque um limite medido na seção anterior a exige — inclusive a escolha de Standard em vez de Express, que é o erro mais fácil de cometer justamente porque parece a opção "mais leve".
- O orquestrador muda de motor, não só de nome. Uma execução Standard do Step Functions aguenta até um ano — seis ordens de grandeza acima do problema deste laboratório. É essa folga, e não nenhuma configuração especial, que remove o teto de 900 s da equação.
- A referência atravessa, o dado não. Um estado do Step Functions aceita até 256 KiB de entrada ou saída. O dataset bruto e o relatório de 45 MB nunca caberiam ali — então o que entra na máquina de estados é a URI do objeto no S3, nunca o conteúdo.
- RunTask.sync é quem escapa do teto de 900 s. O sufixo `.sync` faz o Step Functions aguardar o contêiner terminar e ler o código de saída dele, em vez de disparar e esquecer. A task roda até o processo encerrar; não existe aqui uma cota de serviço análoga ao timeout do Lambda.
- O resultado também sai por referência. A task escreve o PDF final direto no S3 e devolve a URI para o Step Functions — de novo, dentro do teto de 256 KiB, porque o que trafega é o caminho do objeto, não os megabytes do relatório.
- O painel mede a fase que realmente importa. Com ECS é possível separar quanto tempo foi provisionamento de rede e quanto foi processamento real — algo que o `Duration` de uma cadeia de Lambda não distingue: lá, tudo é um único número subindo.
- Falha e aviso são declarativos, não gambiarra. Retry e Catch ficam na definição da máquina de estados, e a publicação de sucesso ou falha é uma integração de serviço nativa — nenhum código escrito à mão para tentar de novo ou para avisar alguém.
- A notificação chega sozinha, sem polling manual. O time financeiro passa a saber que o fechamento terminou pelo mesmo mecanismo que decidiu se ele deu certo — e não mais contando arquivos num bucket para adivinhar se a cadeia de Lambda concluiu.
O que RunTask.sync devolve de graça
Sem essa integração, o padrão seria escrever um Lambda que dispara a task e faz polling em loop até ela terminar — reintroduzindo exatamente o tipo de código que este módulo existe para eliminar. `RunTask.sync` faz o Step Functions aguardar e ler o `exitCode` nativamente; o polling deixa de ser seu problema.
O caminho de uma execução, ponta a ponta
O que entra na máquina de estados nunca é o dado — é onde encontrá-lo. É a mesma disciplina em dois lugares: o payload de um estado do Step Functions tem 256 KiB de teto, e nenhum dos dois arquivos deste laboratório (o dataset de entrada, o relatório de saída) chegaria perto de caber ali.
O StartExecution recebe exatamente isto. Compare com o checkpoint da arquitetura minima, que precisava carregar estado de progresso a cada invocacao — aqui nao ha "progresso" para carregar, porque a task roda como uma unidade so.
{
"datasetUri": "s3://ffv-lab-fechamento/bruto/2026-08/consolidado.parquet",
"relatorioDestinoUri": "s3://ffv-lab-fechamento/saida/2026-08/fechamento.pdf",
"mesReferencia": "2026-08",
"lojas": 30
}A referência que atravessa, não o dado
A URI do dataset de origem tem algumas dezenas de caracteres; o arquivo que ela aponta tem gigabytes. É essa diferença de ordem de grandeza que faz o teto de 256 KiB do Step Functions ser irrelevante na prática — desde que a disciplina de nunca colocar o conteúdo do arquivo dentro do JSON de estado seja mantida em toda a cadeia.
256 KiB não é sugestão
É cota rígida, e falha com `States.DataLimitExceeded` — não trunca silenciosamente, não comprime, não avisa antes. Um estado de teste que tenta devolver o relatório inteiro como string reprova imediatamente, e essa reprovação clara é preferível a descobrir o limite em produção, no mês em que o relatório cresceu.
As decisões, e o que se perde em cada uma
📋 Fechamento mensal que consolida os pedidos de 30 lojas num relatório de até 45 MB, levando de 20 a 35 minutos, com até 3 execuções simultâneas em datas de virada de trimestre; o resto da plataforma já roda inteiro em Lambda desde o laboratório anterior.
Nenhum dos cinco limites medidos nesta seção reprova esta combinação: a execução Standard aguenta até 1 ano contra os 20 a 35 minutos reais; a task Fargate não tem teto de duração documentado; o dado atravessa por referência (URI no S3), então nem os 256 KiB do Step Functions nem os 6 MB do Lambda entram em jogo; e nada aqui compete pelo teto compartilhado de 1.000 execuções da conta, porque não é mais Lambda quem processa. A decisão não é "trocar tudo por Fargate" — é usar cada computação onde ela não bate teto.
Alt: Encadear invocações Lambda (auto-invocação com checkpoint) — Não remove nenhum teto: move a falha do timeout (claro, no log) para idempotência mal projetada e concorrência da conta disputada (silenciosas, e piores). É a arquitetura mínima deste laboratório, e existe para mostrar isso.
Alt: Pedir aumento de cota do timeout do Lambda — Não existe como pedido possível: ao contrário de memória e concorrência, o timeout de 900 s é cota rígida do serviço, sem formulário de aumento no Service Quotas.
Alt: Step Functions Express em vez de Standard — Troca um teto por outro pior: Express tem execução máxima de 5 minutos — menos da metade dos 15 do Lambda que se tentou evitar. Parece a opção "mais leve" e é a mais rápida a reprovar este job específico.
Alt: Uma instância EC2 fixa só para o fechamento — Remove os cinco limites, mas reintroduz o problema que o laboratório anterior resolveu: pagar hora ligada por um processo que roda uma vez por mês. Legítimo quando a carga é genuinamente constante; não é este caso.
| Decisão | Escolha | Alternativas | Motivo | O que se perde |
|---|---|---|---|---|
| Tipo de workflow | Step Functions Standard | Express; Lambda com auto-invocação | 1 ano de teto de execução cobre os 20 a 35 min reais com folga de seis ordens de grandeza | preço por transição de estado é maior que no Express — irrelevante no volume deste job |
| Onde roda o trabalho pesado | task Fargate via RunTask.sync | Lambda encadeado; EC2 fixa; Batch | sem teto de duração documentado, com CPU/memória/disco dimensionáveis pela carga real | cold start de task (~30-60 s de provisionamento) contra ~200 ms de um Lambda quente |
| Como o dado atravessa a orquestração | referência (URI no S3) | payload direto no evento/estado | o relatório de 45 MB nunca caberia nos 256 KiB do Step Functions nem nos 6 MB do Lambda | mais uma chamada de rede (ler do S3) que um payload embutido dispensaria |
| Armazenamento efêmero da task | 30 GiB explícito | 20 GiB padrão; volume EFS | dataset intermediário de 10 a 15 GB precisa de folga, não de exatidão no limite | os 20 GiB padrão são incluídos; os 10 GiB extra que este job pede acima disso cobram por GB-hora — pequeno na escala aqui, mas não é zero |
| Notificação de resultado | integração de serviço SNS do próprio Step Functions | Lambda extra só para notificar; e-mail manual | elimina um componente inteiro que só existiria para publicar uma mensagem | menos controle de formatação da mensagem do que um Lambda dedicado teria |
A dívida que esta arquitetura não paga sozinha
RunTask.sync resolve orquestração e teto de tempo, mas não decide sozinho quantas tasks podem rodar ao mesmo tempo no cluster, nem alerta se o exitCode vem errado sem ninguém olhar o painel. A seção de observabilidade cobre o que ainda precisa de alarme configurado à mão.
Construir: a máquina de estados, e por que STANDARD é a decisão
A troca de motor está inteira nesta definição: `type = "STANDARD"`, um estado `Task` com `RunTask.sync`, e a política de IAM que sustenta essa espera.
# state_machine.tf — o orquestrador declarativo, e por que STANDARD é a decisão
# A definição em si. type = "STANDARD" e nao "EXPRESS" e a linha mais importante
# deste arquivo: Express tem teto de execucao de 5 minutos, menor que os 15 do
# Lambda que este modulo inteiro existe para superar. Escolher Express aqui
# reproduziria o mesmo defeito com um nome mais moderno.
resource "aws_sfn_state_machine" "fechamento_mensal" {
name = "${var.projeto}-fechamento-mensal"
role_arn = aws_iam_role.step_functions.arn
type = "STANDARD"
definition = jsonencode({
Comment = "Consolida o fechamento mensal via task Fargate, sem teto de 900s"
StartAt = "GerarRelatorio"
States = {
GerarRelatorio = {
Type = "Task"
Resource = "arn:aws:states:::ecs:runTask.sync"
Arguments = {
Cluster = aws_ecs_cluster.principal.arn
TaskDefinition = aws_ecs_task_definition.fechamento.arn
LaunchType = "FARGATE"
NetworkConfiguration = {
AwsvpcConfiguration = {
Subnets = aws_subnet.privada[*].id
SecurityGroups = [aws_security_group.task.id]
}
}
# So a URI atravessa a maquina de estados — nunca o dado. O relatorio
# de 45 MB estouraria os 256 KiB de input/output de um estado.
Overrides = {
ContainerOverrides = [{
Name = "fechamento"
Environment = [
{ Name = "DATASET_URI", "Value.$" = "$.datasetUri" },
{ Name = "DESTINO_URI", "Value.$" = "$.relatorioDestinoUri" },
]
}]
}
}
# Falha transiente de infraestrutura (task nao provisionou) tenta de
# novo; falha do PROCESSO (exitCode != 0) cai direto no Catch — retry
# em erro de logica de negocio so atrasaria a descoberta do problema.
Retry = [{
ErrorEquals = ["States.Timeout", "ECS.AmazonECSException"]
IntervalSeconds = 30
MaxAttempts = 2
BackoffRate = 2.0
}]
Catch = [{
ErrorEquals = ["States.ALL"]
Next = "NotificarFalha"
}]
Next = "NotificarSucesso"
}
NotificarSucesso = {
Type = "Task"
Resource = "arn:aws:states:::sns:publish"
Arguments = {
TopicArn = aws_sns_topic.fechamento.arn
Message = "Fechamento mensal concluido"
}
End = true
}
NotificarFalha = {
Type = "Task"
Resource = "arn:aws:states:::sns:publish"
Arguments = {
TopicArn = aws_sns_topic.fechamento.arn
Message = "Fechamento mensal FALHOU — ver historico de execucao"
}
End = true
}
}
})
}
# A politica que o Step Functions realmente precisa para RunTask.sync — nao e
# "acesso total ao ECS". `RunTask` e restrito a UMA revisao de task definition;
# `StopTask`/`DescribeTasks` vao com Resource "*" porque o TaskId so existe
# depois de a task ja ter subido, e a AWS documenta essa restricao como
# necessaria (nao e preguica: e o unico jeito de expressar "a task que EU
# criei", sem saber o ID de antemao). O bloco de events e o que sustenta o
# ".sync": o Step Functions cria uma regra gerenciada do EventBridge para saber
# quando a task termina, e sem essas tres acoes o RunTask.sync nunca retorna.
data "aws_iam_policy_document" "step_functions_ecs" {
statement {
effect = "Allow"
actions = ["ecs:RunTask"]
resources = ["${aws_ecs_task_definition.fechamento.arn_without_revision}:*"]
}
statement {
effect = "Allow"
actions = ["ecs:StopTask", "ecs:DescribeTasks"]
resources = ["*"]
}
statement {
effect = "Allow"
actions = ["events:PutTargets", "events:PutRule", "events:DescribeRule"]
resources = [
"arn:aws:events:${var.regiao}:${data.aws_caller_identity.atual.account_id}:rule/StepFunctionsGetEventsForECSTaskRule"
]
}
statement {
effect = "Allow"
actions = ["iam:PassRole"]
resources = [aws_iam_role.execucao_task.arn, aws_iam_role.task.arn]
# PassRole tem de ser restrito as DUAS roles que a task realmente usa —
# sem isso, o Step Functions poderia entregar a task para rodar com
# QUALQUER role da conta que aceitasse ser assumida por ecs-tasks.
}
}
output "state_machine_arn" {
value = aws_sfn_state_machine.fechamento_mensal.arn
description = "ARN da maquina de estados; StartExecution aceita so datasetUri e relatorioDestinoUri"
}
O `Resource: "*"` que se justifica, e o que ele NÃO abre
`ecs:StopTask` e `ecs:DescribeTasks` levam `Resource: "*"` porque o ID da task só existe depois de ela já ter sido criada por `RunTask` — não há ARN para restringir de antemão. A própria documentação da AWS registra a ressalva: mesmo com essa política ampla, só é possível parar tasks que o PRÓPRIO Step Functions iniciou.
Construir: a task que roda até terminar, não até um relógio mandar parar
O ponto central deste arquivo não é a lógica de geração do relatório — é o dimensionamento: CPU, memória e armazenamento efêmero acima do que o Lambda ofereceria, e nenhuma linha de configuração de timeout de execução, porque não existe uma aqui.
# task_definition.tf — a task sem teto de 900s, e o disco que o Lambda nao dá
resource "aws_ecs_task_definition" "fechamento" {
family = "${var.projeto}-fechamento"
requires_compatibilities = ["FARGATE"]
network_mode = "awsvpc"
# 4 vCPU / 8 GB: acima do 1 vCPU-equivalente do Lambda (que so chega la a
# 1.769 MB de memoria) e sem o teto de 10.240 MB. O par e uma combinacao
# valida da tabela de tamanhos Fargate — confira a tabela antes de escolher
# outra, porque nem todo par CPU x memoria e aceito.
cpu = 4096
memory = 8192
# 30 GB: confortavel acima do padrao de 20 GB do Fargate e MUITO acima do
# teto de 10 GB do /tmp do Lambda, mesmo configurado no maximo. E o limite
# que o dataset intermediario do fechamento (na ordem de 10 a 15 GB antes de
# compactar) bateria se este job ainda estivesse forcado em Lambda.
ephemeral_storage {
size_in_gib = 30
}
execution_role_arn = aws_iam_role.execucao_task.arn
task_role_arn = aws_iam_role.task.arn
container_definitions = jsonencode([{
name = "fechamento"
image = "${aws_ecr_repository.fechamento.repository_url}:${var.sha_da_imagem}"
essential = true
# As duas URIs chegam por variavel de ambiente, sobrescritas pelo
# ContainerOverride do Step Functions — nunca o conteudo do relatorio.
environment = [
{ name = "DATASET_URI", value = "" },
{ name = "DESTINO_URI", value = "" },
]
logConfiguration = {
logDriver = "awslogs"
options = {
"awslogs-group" = aws_cloudwatch_log_group.fechamento.name
"awslogs-region" = var.regiao
"awslogs-stream-prefix" = "fechamento"
}
}
}])
}
resource "aws_sns_topic" "fechamento" {
name = "${var.projeto}-fechamento-mensal"
}
// Program.cs — o processo que roda ate terminar, nao ate um relogio de servico mandar parar
using Amazon.S3;
using Amazon.S3.Transfer;
var datasetUri = Environment.GetEnvironmentVariable("DATASET_URI")
?? throw new InvalidOperationException("DATASET_URI ausente");
var destinoUri = Environment.GetEnvironmentVariable("DESTINO_URI")
?? throw new InvalidOperationException("DESTINO_URI ausente");
using var s3 = new AmazonS3Client();
using var transfer = new TransferUtility(s3);
// Aqui, ao contrario do Lambda, nao existe um teto de tempo documentado pela
// AWS para esta chamada terminar. O que limita a duracao e o que VOCE decide
// — um temporizador proprio, se quiser um — e o custo por segundo de CPU e
// memoria rodando, nunca uma cota de servico.
var (bucketOrigem, chaveOrigem) = AnalisarUriS3(datasetUri);
var caminhoLocal = Path.Combine("/tmp", "dataset.parquet"); // /tmp aqui tem 30 GiB, nao 10
await transfer.DownloadAsync(caminhoLocal, bucketOrigem, chaveOrigem);
var pedidos = await CarregarPedidosDasTrintaLojas(caminhoLocal);
// O trabalho pesado de verdade: 20 a 35 minutos consolidando e renderizando.
// Nenhuma chamada de "continue de onde parei" — o processo e uma unidade so.
var caminhoRelatorio = await GerarRelatorioConsolidado(pedidos); // ~45 MB de PDF
var (bucketDestino, chaveDestino) = AnalisarUriS3(destinoUri);
await transfer.UploadAsync(caminhoRelatorio, bucketDestino, chaveDestino);
// O exitCode e o que o RunTask.sync le para decidir Retry, Catch ou seguir —
// e substitui, de forma nativa, tudo o que a cadeia de Lambda tinha de
// reconstruir a partir de linhas de log.
Console.WriteLine($"fechamento gravado em {destinoUri}");
return 0;
static (string bucket, string chave) AnalisarUriS3(string uri)
{
var semEsquema = uri.Replace("s3://", string.Empty);
var indice = semEsquema.IndexOf('/');
return (semEsquema[..indice], semEsquema[(indice + 1)..]);
}
A combinação de CPU e memória não é livre
Fargate aceita apenas pares específicos de vCPU e memória — 4 vCPU permite de 8 a 30 GB, por exemplo, não qualquer valor entre eles. Escolher um par fora da tabela oficial falha no `RegisterTaskDefinition` com um erro genérico de configuração inválida; confira a tabela antes de escrever o número.
Medir antes de decidir, depois implantar e provar
A ordem importa: as cinco medições abaixo vêm ANTES da arquitetura de produção, porque são elas que a justificam. Rode-as no seu ambiente — os números da Cadência são o que ela mediu, não uma constante universal.
#!/usr/bin/env bash
# medir-antes-de-decidir.sh — cinco números, não uma sensação de "não coube"
PROJETO=ffv-lab-fechamento
REGIAO=us-east-1
# ── Medição 1: duração real do job, fora de qualquer teto ────────────────────
# Roda o MESMO binário como processo local, sem Lambda no caminho. O número
# que sai daqui é a linha de base contra a qual todo limite abaixo é comparado.
time dotnet Fechamento.dll --dataset ./amostra-30-lojas.parquet --destino ./saida.pdf
# Na Cadência: 23 min 40 s no dado de agosto de 2026 — acima dos 900 s do
# Lambda por um fator de 1,6, sozinho já suficiente para reprovar a arquitetura.
# ── Medição 2: tamanho real do dataset intermediário ─────────────────────────
du -sh ./tmp-intermediario/
# Na Cadência: 11 GB antes de compactar — dentro do teto de 10.240 MB do
# /tmp do Lambda configurado no máximo, mas sem nenhuma folga para crescer.
# ── Medição 3: tamanho real do relatório final ────────────────────────────────
du -sh ./saida.pdf
# Na Cadência: 45 MB — sete vezes o teto de 6 MB de resposta síncrona do
# Lambda, e cento e oitenta vezes o teto de 256 KiB de um estado do Step Functions.
# ── Medição 4: quantas execuções concorrentes o pico realmente gera ──────────
aws logs filter-log-events --log-group-name "/ecs/${PROJETO}" \
--start-time "$(date -d '3 months ago' +%s000)" \
--filter-pattern "fechamento gravado" --query 'length(events)' --output text
# Na Cadência: até 3 execuções no mesmo dia em virada de trimestre — contra o
# teto COMPARTILHADO de 1.000 execuções simultâneas do Lambda por conta e por
# região, que a cadeia de auto-invocação disputaria com toda função não relacionada.
# ── Medição 5: cota atual de execuções concorrentes da conta ─────────────────
aws service-quotas get-service-quota --service-code lambda \
--quota-code L-B99A9384 --region "$REGIAO" \
--query 'Quota.Value' --output text
# Confirma o teto que a Medição 4 compara. O valor padrão documentado é 1.000;
# meça o seu, porque cotas sobem com uso e este número muda por conta.
| Limite | Valor medido na Cadência | Teto documentado (07/ago/2026) | Margem |
|---|---|---|---|
| Duração do job | 23 min 40 s (até 35 min no pico) | 900 s (15 min) — Lambda, hard quota | ultrapassa em 1,6× a 2,3×; reprova sozinho |
| Dataset intermediário | 10 a 15 GB | 10.240 MB (10 GB) — /tmp do Lambda, configurável | sem folga; qualquer crescimento reprova |
| Relatório final | até 45 MB | 6 MB (síncrono) / 1 MB (assíncrono) — payload do Lambda | reprova as duas formas de invocação em 7× a 45× |
| Execuções simultâneas | até 3 no pico | 1.000 — concorrência da conta, compartilhada | folgado isoladamente; a cadeia de auto-invocação multiplica esse número por invocação |
| Imagem de dependência | ~300 MB (motor de PDF) | 50 MB zip / 250 MB descompactado / 10 GB imagem | obriga empacotamento como imagem de contêiner, aceito por Lambda e por Fargate |
Com os cinco números medidos, a implantação e as provas confirmam que a arquitetura escolhida de fato não bate teto nenhum deles.
# provas.sh — cinco medições; nenhuma decisão fica só na afirmação
PROJETO=ffv-lab-fechamento
REGIAO=us-east-1
# ── Prova 1: o timeout do Lambda é hard quota, não é possível pedir aumento ──
# Tenta reconfigurar o timeout acima do teto. Deve falhar com erro do
# parâmetro, não com "aguarde aprovação" como aconteceria com concorrência.
aws lambda update-function-configuration --function-name "${PROJETO}-orquestrador" \
--timeout 901 2>&1 | grep -i "InvalidParameterValueException" \
&& echo "OK: confirmado — 900s é o teto e não aceita valor maior" \
|| echo "FALHA DA PROVA: se aceitou, o teto documentado mudou — reconfirme na doc oficial"
# ── Prova 2: RunTask.sync aguarda o exitCode, não dispara e esquece ──────────
aws stepfunctions start-execution --state-machine-arn "$(terraform output -raw state_machine_arn)" \
--input '{"datasetUri":"s3://ffv-lab-fechamento/bruto/teste.parquet","relatorioDestinoUri":"s3://ffv-lab-fechamento/saida/teste.pdf"}' \
--query 'executionArn' --output text
# Espere e confira o status:
aws stepfunctions describe-execution --execution-arn "<ARN acima>" \
--query '{status:status,duracao_s:(stopDate-startDate)}' --output table
# Esperado: status SUCCEEDED, e a duração bate com o tempo real do processo —
# não com nenhum múltiplo de 900 s.
# ── Prova 3: o limite de 256 KiB de payload por estado é real, não teórico ───
# Tenta passar um trecho grande como output de um estado de teste.
aws stepfunctions test-state --state-definition '{
"Type":"Pass",
"Result": "'"$(head -c 300000 /dev/urandom | base64)"'"
}' --role-arn "$(terraform output -raw sfn_role_arn)" 2>&1 \
| grep -i "States.DataLimitExceeded" \
&& echo "OK: o limite de 256 KiB rejeitou o payload — é por isso que só a URI atravessa" \
|| echo "confira manualmente: o teste pode ter usado payload abaixo do teto"
# ── Prova 4: a concorrência da conta é compartilhada, não é sua sozinho ──────
# Dispara 20 invocações em paralelo de uma função QUALQUER e mede throttle.
for i in $(seq 1 20); do
aws lambda invoke --function-name "${PROJETO}-qualquer-outra-funcao" \
--invocation-type Event /dev/null &
done; wait
aws cloudwatch get-metric-statistics --namespace AWS/Lambda --metric-name Throttles \
--dimensions Name=FunctionName,Value="${PROJETO}-qualquer-outra-funcao" \
--start-time "$(date -u -d '5 min ago' +%FT%TZ)" --end-time "$(date -u +%FT%TZ)" \
--period 60 --statistics Sum --query 'Datapoints[].Sum' --output text
# Se a cadeia de auto-invocação do desenho mínimo estivesse rodando ao mesmo
# tempo, este número tende a subir — a prova de que o teto é da CONTA, não da função.
# ── Prova 5: o exitCode diferente de zero realmente cai no Catch ─────────────
# Force o contêiner a sair com erro (ex.: dataset corrompido de propósito) e
# confira o histórico de execução.
aws stepfunctions get-execution-history --execution-arn "<ARN de uma execução forçada a falhar>" \
--query "events[?type=='ExecutionFailed' || type=='TaskStateExited']" --output table
# Esperado: o Catch levou a execução a "NotificarFalha" — sem nenhum try/catch
# escrito à mão para isso.
| Prova | Comando | Resultado que aprova | O que reprova, e o que significa |
|---|---|---|---|
| 1 · Timeout é cota rígida | `update-function-configuration --timeout 901` | erro de parâmetro inválido, imediato | se aceitou, o teto documentado mudou — reconfirme antes de seguir |
| 2 · RunTask.sync aguarda o processo real | `describe-execution` após `start-execution` | status SUCCEEDED com duração igual ao tempo real do processo | duração multipla de 900s sugere que algo ainda está fatiando o trabalho |
| 3 · 256 KiB é real | `test-state` com payload de 300 KB | `States.DataLimitExceeded` | se passou, o payload de teste ficou abaixo do teto — repita com mais dado |
| 4 · Concorrência é da conta | throttle de uma função QUALQUER durante carga paralela | `Throttles` sobe quando o teto da conta é pressionado | zero throttle não prova ausência de risco — prova que o teste não chegou perto do teto |
| 5 · exitCode decide o caminho | `get-execution-history` de uma execução forçada a falhar | evento leva a `NotificarFalha` sem código de tratamento escrito à mão | se o Catch não disparou, `ErrorEquals` na definição não cobre o erro real emitido |
Quebrar de propósito: três falhas e o diagnóstico
As três reproduzem, sob controle, o que a arquitetura mínima esconde em produção.
| Falha | Como provocar | Sintoma | Onde olhar | Correção |
|---|---|---|---|---|
| Cadeia de auto-invocação para no meio | derrube a permissão de escrita no bucket de saída na invocação 10 de 15 | catorze fatias no bucket, nenhum relatório completo, nenhum erro visível ao financeiro | contagem de objetos no bucket contra o total esperado de lojas | não corrigir a cadeia — trocar por RunTask.sync, que falha com um exitCode explícito |
| Mesma loja processada duas vezes | force a invocação a falhar DEPOIS de gravar a fatia e ANTES de atualizar o checkpoint | total do fechamento maior que a soma real dos pedidos das 30 lojas | comparar o total do relatório com uma contagem independente da fonte | idempotência real (chave de deduplicação por loja), ou eliminar o checkpoint trocando de arquitetura |
| Task Fargate sem armazenamento suficiente | defina `ephemeral_storage.size_in_gib = 20` com um dataset de 25 GB | processo falha por disco cheio a poucos minutos do fim, depois de já ter gasto tempo de CPU | exitCode diferente de zero e mensagem de "No space left on device" no log da task | dimensionar o efêmero pelo dataset medido, com folga — 200 GiB é o teto disponível |
A falha que ninguém detecta sem medir de propósito
A duplicidade de loja processada duas vezes é a mais perigosa das três porque o fechamento TERMINA, publica um relatório e ele parece correto — o erro está no valor consolidado, não na existência do arquivo. Nenhum alarme de infraestrutura pega isso; só uma conferência de total contra uma fonte independente pega.
Segurança: o que muda quando o trabalho pesado sai do Lambda
Trocar de computação move a superfície de risco: de "o que uma função pode fazer quando invocada" para "o que uma task pode fazer enquanto roda por 30 minutos sem supervisão".
| Risco | Probabilidade | Impacto | Controle preventivo | Detecção | Resposta |
|---|---|---|---|---|---|
| Task role ampla demais (lê e escreve em qualquer bucket) | média | alto | role específica da task, escopo por bucket/prefixo do fechamento, nunca `s3:*` | IAM Access Analyzer sobre uso real da role | derivar a política do uso medido — é o L41 |
| Cadeia de auto-invocação disparada por evento malicioso | baixa | médio | validar origem do evento; a solução Fargate elimina o mecanismo por completo | CloudTrail em invocações fora do agendamento esperado | revogar a permissão de invocação direta; mover para o desenho de produção |
| Relatório com dado sensível de 30 lojas em bucket sem restrição | média | alto | bucket policy restritiva, criptografia com KMS, bloqueio de acesso público por padrão | AWS Config regra de bucket público | corrigir a policy; auditar quem acessou enquanto esteve exposto |
| `iam:PassRole` sem escopo na role do Step Functions | baixa | alto | restringir a APENAS a execution role e a task role usadas, nunca `Resource: "*"` | CloudTrail em `PassRole` para role fora das duas esperadas | corrigir a política; auditar se alguma task rodou com role indevida |
| Concorrência da conta esgotada por outra equipe derruba o fechamento | baixa | médio | mover o trabalho pesado para Fargate remove a disputa pelo teto compartilhado do Lambda | métrica `Throttles` da função crítica | concorrência reservada não cria capacidade — só redistribui o teto existente |
O `*` que aparece na política, e por que ele se justifica
`ecs:StopTask` e `ecs:DescribeTasks` não aceitam restringir por ARN de task específica no momento em que a política é escrita, porque o ID da task só existe depois de o `RunTask` já ter criado uma. A própria documentação da AWS registra que, mesmo com esse `*`, só é possível parar tasks iniciadas pelo próprio Step Functions — a regra não é "nunca use `*`", é "todo `*` tem de ter uma frase explicando por que não pode ser mais estreito".
Uma equipe tenta aumentar o timeout de uma função Lambda de 900 para 1200 segundos pelo console e recebe um erro de valor inválido. Qual é a explicação correta?
Observabilidade: as perguntas que o painel tem de responder
| Pergunta | Métrica ou consulta | O que significa mudar | Limiar inicial |
|---|---|---|---|
| A execução terminou dentro do esperado? | `describe-execution` → `status` e duração | FAILED indica Catch acionado; duração muito acima da linha de base indica dataset maior | > 45 min investigar |
| A task Fargate morreu por falta de recurso? | razão de parada da task (`stoppedReason`) | "OutOfMemory" ou disco cheio exigem redimensionar CPU/memória/efêmero | qualquer exitCode != 0 |
| Alguma execução bateu no limite de payload entre estados? | contagem de `States.DataLimitExceeded` no histórico | indica que algum estado tentou carregar dado em vez de referência | > 0 |
| A concorrência compartilhada da conta está perto do teto? | utilização da cota no Service Quotas | perto de 1.000 significa que QUALQUER função pode começar a ser throttled, não só a sua | > 80% de utilização |
| Qual fase do RunTask domina a duração? | timestamps de `describe-tasks` (provisionamento vs execução) | provisionamento alto sugere problema de rede; execução alta é o job em si crescendo | provisionamento > 2 min investigar |
| O total do relatório bate com uma fonte independente? | consulta de conferência no dataset de origem | divergência é o único sinal de duplicidade — nenhuma métrica de infraestrutura pega isso | qualquer divergência |
A métrica que engana quando falta contexto
`ConcurrentExecutions` do Lambda mostra o uso da SUA função, não da conta inteira. Uma função com uso baixo pode mesmo assim ser throttled porque outra função, sem relação nenhuma, consumiu o teto compartilhado. Confira sempre a utilização da cota no Service Quotas, não só a métrica por função.
Escala: 10, 10 mil, 1 milhão — e o que muda quando o volume cresce
| Volume | O que acontece | O que passa a doer | O que fazer |
|---|---|---|---|
| 30 lojas, 1 execução/mês | job de ~24 min, uma task por vez | nada; é o cenário deste laboratório | nada |
| 300 lojas, 1 execução/mês | job proporcionalmente maior, possivelmente 4h+ | ainda dentro do teto de 1 ano do Standard, mas o dataset intermediário pode passar de 100 GB | redimensionar CPU/memória/efêmero da task pela medição nova — o teto do Fargate é 32 vCPU / 244 GB / 200 GiB |
| 30 lojas, 50 execuções/mês (relatório diário por filial) | muitas tasks curtas em vez de uma longa | cota de tasks Fargate simultâneas por conta/região pode ser tocada em picos | não verificado neste módulo: confira o valor atual da sua cota no Service Quotas antes do pico |
| Pico de 20 execuções no mesmo minuto | Step Functions aguenta (1.000.000 de execuções abertas por conta) | o gargalo passa a ser a cota de tasks Fargate simultâneas, não a orquestração | escalonar o disparo (`EventBridge` com jitter) ou pedir aumento de cota do ECS antes de depender do pico |
| Falha de AZ durante a execução | a task perde a sub-rede em que rodava | sem retry configurado, a execução falha e o Catch dispara sem reprocessar | sub-redes em múltiplas AZs na `NetworkConfiguration`, e o `Retry` do Terraform cobre a reexecução |
O número que este laboratório não verificou
A cota de tasks Fargate simultâneas por conta e por região existe e é ajustável pelo Service Quotas, mas o valor padrão não foi confirmado na documentação consultada para este módulo. Antes de depender de um pico de dezenas de execuções simultâneas, meça esse número na sua conta — é exatamente o tipo de afirmação que este laboratório ensina a não aceitar sem checar.
Custo: o que trocar de computação acrescenta à fatura
A pergunta certa não é "Fargate é mais caro que Lambda por segundo" — depende do tamanho da task. A pergunta certa é: o Lambda sequer completaria este job pelo preço que fosse.
| Cenário | Volume | O que acrescenta | Tendência | Otimização |
|---|---|---|---|---|
| Protótipo | 1 execução/mês, task 2 vCPU / 4 GB | minutos de Fargate por mês, e transições de estado do Step Functions | desprezível | nenhuma; o volume não justifica atenção aqui |
| Produção pequena | 1 execução/mês, task 4 vCPU / 8 GB por ~24 min | a maior linha nova é o tempo de CPU/memória da task — pequena em termos absolutos | baixa e previsível | dimensionar CPU/memória pelo medido, não por excesso de segurança |
| Alta escala | 50 execuções/mês, tasks maiores por dataset crescido | custo de Fargate cresce proporcional a execuções × duração × tamanho da task | linha visível, mas ainda previsível pela fórmula | tasks menores em paralelo (particionar por grupo de lojas) trocam tempo por custo total |
| Dimensão | Cobra por | Cuidado |
|---|---|---|
| Fargate | vCPU-segundo e GB-segundo enquanto a task roda | proporcional a execuções × duração × tamanho — encurtar a duração real economiza aqui, não só reduzir CPU |
| Transições de estado do Step Functions | por transição, Standard | um workflow com poucos estados (como este) gera poucas transições por execução |
| Armazenamento efêmero da task | incluído no tempo de execução, sem cobrança separada até certo teto | confirme o teto gratuito atual antes de dimensionar generosamente por hábito |
| S3 (dataset e relatório) | GB armazenado e GB de transferência | dataset bruto e relatórios antigos acumulam; ciclo de vida evita crescimento sem fim |
| SNS | por mensagem publicada | valor pequeno e fixo; não é onde se economiza |
O custo que a auto-invocação escondia
A arquitetura mínima parecia mais barata porque "só usa Lambda, que já está pago". Na prática, cada invocação da cadeia é cobrada separadamente, e uma cadeia de 15 invocações de 800 s cada custa mais tempo de computação faturado que uma única task Fargate rodando os mesmos 23 minutos direto — sem contar o tempo de engenharia gasto depurando duplicidade que a fatura não mostra.
Well-Architected nos seis pilares
| Pilar | Situação ao fim deste laboratório | Risco que fica | Melhoria | Prioridade |
|---|---|---|---|---|
| Excelência operacional | falha e sucesso são declarativos (Retry/Catch), com notificação nativa | ainda não há reprocessamento automático de uma execução falha, só notificação | redrive de execução do Step Functions Standard (janela de 14 dias) | média |
| Segurança | task role escopada, `PassRole` restrito às duas roles corretas | bucket do relatório final ainda depende de policy configurada corretamente à mão | AWS Config regra que reprova bucket sem bloqueio de acesso público | alta |
| Confiabilidade | sem teto de duração documentado para a task; execução sobrevive a queda de uma AZ com retry | cota de tasks Fargate simultâneas não medida — pode surpreender em pico | medir a cota real e reservar capacidade antes do pico (seção de escala deste módulo) | média |
| Eficiência de performance | CPU/memória/efêmero dimensionados pela medição real do job | dimensionamento é estático; não se ajusta sozinho se o dataset crescer | reavaliar o par CPU/memória a cada trimestre com dado novo | baixa |
| Otimização de custos | paga só pelos minutos reais de execução, sem hora ociosa | nenhum ciclo de vida configurado nos objetos intermediários do S3 | política de expiração para o dataset bruto e relatórios com mais de 90 dias | média |
| Sustentabilidade | task dimensionada pelo uso medido evita superprovisionamento | nenhum | nenhuma ação pendente | baixa |
Evolução em níveis: quando cada limite volta a importar
A terceira arquitetura não é um desenho novo: é a resposta a QUANDO os cinco limites voltam a apertar, e o que se troca em cada nível.
Tudo em Lambda, sem medir duração nem tamanho de dataset. Legítimo enquanto o job é pequeno de verdade — é onde a Cadência começou.Cinco limites medidos, Step Functions Standard orquestrando uma task Fargate via RunTask.sync, dado passando por referência.Redrive de execução falha, alarme de exitCode diferente de zero, conferência automática do total contra fonte independente.Distributed Map do Step Functions processando N lojas em paralelo, com cota de Fargate confirmada e reservada antes do pico.A regra "quando sair do Lambda" vira serviço reutilizável entre times, com os cinco limites documentados e um checklist antes de qualquer job novo.Insights automáticos sobre os relatórios já gerados — tendência mês a mês, anomalia por loja — como camada SOBRE o dado determinístico, não substituindo a geração dele.A ordem não é negociável
Distributed Map no nível 4 depende de a parte pesada já rodar fora do Lambda — paralelizar uma cadeia de auto-invocação só paralelizaria o problema de idempotência do nível 1. É o nível 2 que remove o defeito estrutural; os níveis seguintes escalam uma base que já não bate teto, não escondem uma que bate.
Onde IA entra nesta arquitetura, e onde não entra
Neste módulo, IA não decide nada do que foi resolvido. Os cinco limites medidos são fatos documentados pela AWS — 900 segundos, 6 MB, 256 KiB, 1.000 execuções, 20 a 200 GiB — e nenhum modelo melhora um número que já está escrito na página de cotas. Forçar IA aqui seria decorar exatamente o tipo de decisão que precisa ser determinística.
Há um lugar honesto para IA, e ele fica DEPOIS do relatório pronto, não na decisão de onde ele roda: resumir tendência entre meses e sinalizar loja com padrão fora do comum para revisão humana antes de fechar. É o mesmo caso do enriquecimento em lote — classificar ou resumir um acervo sem ninguém esperando — que o catálogo trata no L95.
| Pergunta | Resposta honesta para este módulo |
|---|---|
| Qual problema a IA resolveria? | nenhum dos cinco limites medidos; eles são fato de documentação, não julgamento |
| Onde ela ajudaria de verdade? | resumir tendência entre fechamentos e sinalizar loja com número fora do padrão, como leitura assistida sobre o relatório já pronto |
| Por que uma regra não bastaria ali? | uma regra de limiar simples ("variação acima de X%") cobre a maior parte; IA só se justificaria com histórico grande o bastante para aprender padrão sazonal que um limiar fixo não capturaria |
| Qual o risco? | sumarização que esconde um número real sob uma interpretação — o relatório determinístico continua sendo a fonte de verdade, nunca o resumo |
| Por que não agora? | a Cadência tem poucos meses de histórico; um classificador sobre isso é aparência de estatística, não estatística |
O uso de IA que parece atraente e é armadilha aqui
Pedir a um modelo para "decidir se o job cabe no Lambda" trocaria um fato verificável — o timeout é 900 segundos, ponto — por uma resposta probabilística sobre algo que não é probabilístico. Onde existe um número documentado pela AWS, IA só acrescenta latência e a chance de errar com confiança.
Anti-padrões deste laboratório
| Anti-padrão | Por que alguém faz | Por que é problema | Sintoma em produção | Forma correta | Quando é aceitável |
|---|---|---|---|---|---|
| Encadear invocações Lambda para escapar do timeout | parece "só preciso continuar de onde parei"; é a menor mudança que faz o erro sumir do log | não remove nenhum limite: move a falha do timeout (claro) para idempotência e concorrência (silenciosas) | duplicidade de dado processado duas vezes; cadeia que para no meio sem terminar | Step Functions Standard orquestrando a task via RunTask.sync | nunca para trabalho com estado a preservar; aceitável só para paginação idempotente genuína e curta |
| Pedir aumento de cota do timeout do Lambda | funcionou para memória e para concorrência, então parece que timeout também é ajustável | timeout é cota RÍGIDA — não existe formulário de aumento, e insistir só atrasa a decisão certa | ticket de suporte fechado como "não é possível aumentar este valor" | medir a duração real do job e trocar de computação se ultrapassar 900 s | nunca; não é uma opção que exista |
| Escolher Step Functions Express por parecer "mais leve" | o nome sugere simplicidade, e o preço por transição é menor | Express tem teto de 5 minutos — pior que os 15 do Lambda para qualquer job que já não coubesse nele | execução falha com `States.Timeout` aos 5 minutos, sem nenhum erro de configuração aparente | Standard para qualquer job que possa passar de alguns minutos | Express serve para chamadas rápidas de alto volume onde 5 min é folga real, não para batch |
| Passar o relatório inteiro como input/output de um estado | "funciona no teste com arquivo pequeno" e só quebra em produção com dado real | bate no teto de 256 KiB, que é hard quota — falha, não trunca silenciosamente | `States.DataLimitExceeded` no histórico de execução, sem indicação clara de qual estado | passar a URI do objeto no S3, nunca o conteúdo | nunca; o teto vale mesmo para payload "pequeno o bastante" hoje que pode crescer amanhã |
| Aumentar memória do Lambda só para "ganhar mais /tmp" | memória é o dial mais visível no console, e parece resolver qualquer limite | memória e /tmp são cotas relacionadas mas independentes — aumentar uma nem sempre resolve a outra, e nenhuma resolve timeout | job continua falhando por timeout mesmo depois do aumento de memória | medir qual dos cinco limites é o gargalo real antes de mexer em qualquer configuração | aceitável só quando a medição já confirmou que /tmp era de fato o gargalo isolado |
| Manter tudo em Lambda "porque já está tudo em Lambda" | reescrever para outra computação parece retrocesso depois do esforço de migrar no L26 | sunk cost não é critério técnico; o L26 resolveu um problema diferente (custo de ocioso), não duração de job | workaround sobre workaround até o sistema ficar frágil demais para qualquer um mexer | usar Lambda onde ele serve e Fargate onde não serve, na mesma plataforma | aceitável enquanto o job realmente couber nos cinco limites — é o caso da maior parte da API |
Quando algo não funciona
| Sintoma | Causa provável | Como investigar | Onde olhar | Correção |
|---|---|---|---|---|
| "Task timed out after 900.00 seconds" no log | job real passa do teto rígido de 900 s | medir a duração real fora do Lambda (processo local ou contêiner) | CloudWatch Logs, linha REPORT | mover a parte pesada para Fargate via RunTask.sync |
| Erro ao retornar payload grande numa invocação síncrona | resposta maior que 6 MB | medir o tamanho real do objeto retornado | tamanho do corpo de resposta | retornar referência (URI no S3) em vez do arquivo |
| Invocação assíncrona falha sem exceção clara na aplicação | evento maior que 1 MB | checar o tamanho do payload assíncrono enviado | DLQ configurada e CloudWatch | reduzir o evento a uma referência, nunca ao dado em si |
| `ThrottlingException` em funções sem relação entre si | concorrência da conta (1.000) estourada por outra função | checar utilização da cota compartilhada no Service Quotas | métrica `Throttles` de todas as funções da conta | concorrência reservada NÃO ajuda — só reparte o teto existente; a correção é sair do Lambda para o trabalho pesado |
| `States.DataLimitExceeded` na execução | input/output de um estado passou de 256 KiB | ler o histórico de execução no console do Step Functions | evento de erro no histórico da execução | passar URI do S3 em vez do payload |
| Execução falha aos 5 minutos sem erro na task | workflow criado como EXPRESS em vez de STANDARD | checar o `type` declarado na definição da máquina de estados | `describe-state-machine` → `type` | recriar como STANDARD — o tipo não é alterável depois de criado |
| "No space left on device" dentro do contêiner | /tmp (Lambda) ou efêmero (Fargate) no teto configurado | comparar o tamanho real do dataset intermediário com o configurado | métrica de erro + tamanho do dataset | aumentar até 10 GB (Lambda) ou até 200 GiB (Fargate) conforme onde o job roda |
A pergunta que resolve metade destes casos
Antes de mexer em qualquer parâmetro, pergunte: qual dos cinco limites este sintoma aponta — duração, payload síncrono/assíncrono, disco, concorrência ou pacote de deployment? Os sintomas se parecem ("não funcionou", "não terminou"), mas a correção de um não resolve os outros quatro.
Limpeza: o que o destroy não leva
Este laboratório acrescenta poucos recursos com custo próprio, e dois deles sobrevivem ao terraform destroy se você não conferir.
# 1. Derrube o que o Terraform administra.
terraform destroy -auto-approve
# 2. OBJETOS NO S3: o bucket pode sobreviver vazio, mas o dataset bruto e os
# relatorios gerados durante os testes NAO saem sozinhos.
aws s3 ls s3://ffv-lab-fechamento/ --recursive --summarize | tail -3
aws s3 rm s3://ffv-lab-fechamento/ --recursive
# 3. GRUPO DE LOGS da task: tem retencao propria, independente do ciclo do
# Step Functions ou da task definition.
aws logs delete-log-group --log-group-name /ecs/ffv-lab-fechamento 2>/dev/null || true
# 4. TASKS AINDA RODANDO: se voce interrompeu um teste no meio, a task Fargate
# continua cobrando por vCPU e memoria ate terminar ou ser parada.
aws ecs list-tasks --cluster ffv-lab --query "taskArns" --output text
aws ecs stop-task --cluster ffv-lab --task "<arn-de-cada-task-listada>" \
--reason "limpeza do laboratorio" 2>/dev/null || true
# 5. EXECUCOES ABERTAS do Step Functions: nao cobram por hora, mas ficam
# "rodando" indefinidamente se a task associada nunca voltar um exitCode.
aws stepfunctions list-executions \
--state-machine-arn "$(terraform output -raw state_machine_arn)" \
--status-filter RUNNING --query "executions[].executionArn" --output text
# 6. Prova final: nada com a tag do projeto de pe.
aws resourcegroupstaggingapi get-resources \
--tag-filters Key=Projeto,Values=ffv-lab-fechamento \
--query "ResourceTagMappingList[].ResourceARN" --output table| Recurso | Sai no destroy? | Cobra parado? | Por que fica |
|---|---|---|---|
| Máquina de estados e task definition | sim | não | revisões da task definition ficam registradas sem custo |
| Objetos no S3 (dataset e relatórios) | não | sim, GB-mês | o bucket em si sai vazio; o conteúdo gravado durante os testes não |
| Grupo de logs | depende de `skip_destroy` | sim, retenção | tem ciclo próprio; sobrevive ao serviço que o alimentava |
| Task Fargate interrompida no meio de um teste | não automaticamente | sim, por segundo de CPU/memória | só termina de cobrar quando o processo acaba ou é parada explicitamente |
| Execução do Step Functions em RUNNING | não | não | não cobra parada, mas fica “pendurada” se a task nunca devolver exitCode — pare-a manualmente |
| Tópico SNS | sim se em Terraform | centavos | tópico criado à mão no console não aparece no estado |
Resumo: limite, peça e motivo
| Limite medido | Peça que resolve | Por que ela, e não outra |
|---|---|---|
| Timeout de 900 s (hard quota, não ajustável) | task Fargate via RunTask.sync | sem teto de duração documentado; a execução roda até o processo terminar |
| Payload síncrono de 6 MB / assíncrono de 1 MB | relatório gravado direto no S3 | nenhum arquivo de dezenas de MB cabe numa invocação de Lambda de qualquer forma |
| /tmp de até 10.240 MB no Lambda | armazenamento efêmero de até 200 GiB no Fargate | dataset intermediário de 10 a 15 GB não tem folga no teto do Lambda |
| Concorrência de 1.000 compartilhada pela conta | trabalho pesado sai do Lambda por completo | remove a disputa pelo teto compartilhado — não é reserved concurrency, é sair do pool |
| 256 KiB de payload por estado do Step Functions | URI como input/output, nunca o dado | a mesma disciplina que já era necessária para o payload do Lambda, aplicada de novo |
| 5 min do Step Functions Express (armadilha, não solução) | Step Functions STANDARD | Express reproduziria um teto pior que o do Lambda que se tentou evitar |
| Falha | O que a protege | O que ela NÃO protege |
|---|---|---|
| Job que não termina em 900 s | task Fargate sem teto de duração documentado | um job que um dia ultrapasse 1 ano — teoricamente possível, praticamente irrelevante aqui |
| Payload maior que o teto de invocação | passagem por referência (S3) | esquecer a disciplina e colocar dado bruto num estado de teste — o gate reprova, não previne |
| Disco cheio no meio do processamento | armazenamento efêmero dimensionado com folga | crescimento do dataset sem revisão periódica do dimensionamento |
| Concorrência roubada por outra função | trabalho pesado fora do pool compartilhado do Lambda | o restante da API ainda compete pelo mesmo teto de 1.000 entre si |
| Duplicidade de dado processado duas vezes | ausência de checkpoint — a task é uma unidade só | erro de lógica dentro da própria task, que nenhuma arquitetura de orquestração pega sozinha |
- O EventBridge Scheduler dispara a execução mensal do Step Functions.
- A execução recebe só as URIs do dataset de origem e do relatório de destino.
- O estado Task chama RunTask.sync e passa a AGUARDAR o contêiner terminar.
- A task Fargate processa por 20 a 35 minutos, sem nenhum relógio de serviço cobrando.
- A task lê o dataset pela URI recebida e grava o relatório final direto no S3.
- O contêiner termina e devolve um exitCode; RunTask.sync o lê nativamente.
- Sucesso segue para a publicação no SNS; falha cai no Catch declarado na definição.
- O time financeiro recebe o link do relatório pronto, sem contar objeto em bucket.
Perguntas frequentes
❓ O Lambda aceita processar por mais de 15 minutos com algum ajuste de cota?
❓ Por que encadear invocações de Lambda para escapar do timeout é considerado anti-padrão?
❓ Qual é o limite de payload síncrono e assíncrono do Lambda?
❓ Quanto espaço em disco o /tmp do Lambda oferece?
❓ Step Functions Express e Standard têm o mesmo limite de duração de execução?
❓ Por que o payload que passa entre estados do Step Functions tem limite de 256 KB?
❓ Qual é o teto de tamanho do pacote de deployment do Lambda?
❓ Quando faz sentido voltar de Lambda para um contêiner?
Fixando
Um job de 20 minutos é movido de Lambda para uma task Fargate orquestrada por Step Functions. A equipe cria a máquina de estados como tipo EXPRESS para "economizar no preço por transição". O que acontece na primeira execução real?
Um pipeline de Step Functions passa o conteúdo de um relatório de 2 MB, gerado por um estado anterior, diretamente como output para o próximo estado. O que acontece?
Conhecimentos, próximo módulo e documentação
| Item | Conteúdo |
|---|---|
| Conhecimentos anteriores necessários | L26 (API 100% serverless) no ar, Lambda e ECS/Fargate básicos, Terraform e .NET 8 |
| Conhecimentos adquiridos | os cinco limites que decidem se um job cabe em Lambda, com o valor atual de cada um; por que auto-invocação com checkpoint troca falha clara por falha silenciosa; a diferença de teto entre Step Functions Standard e Express; RunTask.sync como substituto nativo de polling manual |
| Limitação que fica | a cota de tasks Fargate simultâneas por conta e região não foi medida neste módulo — confira antes de depender de picos de dezenas de execuções ao mesmo tempo |
| Próximo exemplo recomendado | L34 — EKS quando ECS não basta. Usa o mesmo critério deste módulo (medir antes de trocar de plataforma), aplicado à decisão entre ECS e Kubernetes |
| Também habilitado por este módulo | qualquer job em lote que hoje esteja forçado em Lambda pode ser auditado pela mesma tabela de cinco limites, sem repetir a medição do zero |
| Data da última validação técnica | 7 de agosto de 2026 |
Documentação oficial consultada: AWS Lambda quotas — timeout, memória, payload, `/tmp`, concorrência e pacote de deployment, todos como cotas atuais em 07/ago/2026; AWS Step Functions service quotas — a distinção exata entre execução máxima Standard (1 ano) e Express (5 minutos), e o teto de 256 KiB de input/output por estado; Amazon ECS — troubleshoot task CPU/memory error — as combinações válidas de vCPU e memória para task Fargate, até 32 vCPU e 244 GB; e a página de integração `connect-ecs.html`, fonte da política de IAM exata para `RunTask.sync` usada na seção de construção. Os valores de preço não aparecem neste módulo por decisão: use o AWS Pricing Calculator, porque preço varia por região e envelhece mais rápido que o conteúdo.
O que não foi verificado, e você deve conferir na sua conta
A cota padrão de tasks Fargate simultâneas por conta e por região não foi confirmada na documentação consultada para este módulo — meça a sua no Service Quotas antes de depender de um pico com muitas execuções ao mesmo tempo. Os tempos de processamento citados (23 min 40 s, dataset de 10 a 15 GB, relatório de 45 MB) são os medidos no cenário de exemplo da Cadência, e servem como ordem de grandeza — meça os seus antes de decidir pelo mesmo caminho, porque é exatamente essa disciplina que este módulo ensina.
Terminou de ler?
Marcar como concluído registra o XP, mantém sua sequência e coloca 3 cartas deste módulo na fila de revisão espaçada.
Próximos passos sugeridos
Temas deste módulo
Discussão
Carregando comentários…