Loop recursivo no Lambda: quatro guardrails comparados no Sovereign Cloud
Ouvir artigo
Voz do FernandoFernando · 14:58
Com tecnologia Amazon Polly + OmniVoice
A detecção de loop recursivo do Lambda chegou ao European Sovereign Cloud em 10 de setembro de 2026. Ela corta a cadeia em ~16 invocações, mas só entre Lambda, SQS, SNS e S3, e o aviso pode levar 3,5 horas. Comparo quatro guardrails, detecção nativa, separação estrutural, teto de concorrência e contador de saltos na aplicação, e digo quando cada um basta.
Em 16 anos operando plataformas financeiras, o incidente de Lambda que mais me custou não foi um bug de lógica, foi uma variável de ambiente com o mesmo valor para bucket de origem e bucket de destino. A função escrevia no bucket que a disparava, o S3 gerava outro evento, e em minutos a concorrência da conta inteira estava ocupada com trabalho inútil. A chegada da detecção de loop recursivo ao AWS European Sovereign Cloud, em 10 de setembro de 2026, é boa notícia para quem migrou carga regulada para eusc-de-east-1. Mas a pergunta útil não é 'o Lambda agora me protege?', é 'de qual loop, com que atraso, e o que ainda fica por minha conta?'
O que o Sovereign Cloud muda na equação
Partição isolada: o European Sovereign Cloud é uma partição própria (aws-eusc), com uma única região, eusc-de-east-1, na Alemanha, operada por residentes da UE. Não é 'mais uma região': é um ambiente logicamente separado, com dois Availability Zones em vez de três e cerca de 90 serviços no lançamento de janeiro de 2026. Quem chegou lá vindo de eu-central-1 descobre rápido que ferramentas do dia a dia faltam: IAM Identity Center, Security Hub, Inspector, CloudFront, e que aumento de quota costuma passar por ticket de suporte.
Por que isso importa para loop recursivo: em uma partição com menos serviços de segurança e menos automação de quota, a rede de segurança que você tinha na partição comercial encolhe. Cost Anomaly Detection e alarmes de billing continuam sendo a sua defesa de último recurso, mas cada um precisa ser verificado na própria partição, não assumido. A detecção nativa do Lambda chegou ao EUSC quase três anos depois de estrear nas regiões comerciais (julho de 2023 para SQS, SNS e Lambda; outubro de 2024 para S3). Esse intervalo é a lição: paridade de feature em partição soberana não é automática. Uma carga que dependia de um guardrail gratuito e invisível na partição comercial passou meses sem ele no EUSC sem ninguém perceber. É por isso que comparo quatro mecanismos e não recomendo apenas o que acabou de chegar.
Mesma mecânica, mesmo limite: a documentação em docs.aws.eu traz a mesma tabela de SDK mínimo e o mesmo teto de aproximadamente 16 invocações por cadeia. O que difere é o entorno.
Como a detecção nativa funciona, e onde ela para
Mecanismo: o Lambda pega carona no header de trace do X-Ray. Quando SQS, SNS ou S3 entregam um evento, ele vem anotado com um primitivo Lineage, um hash do recurso mais um contador, algo como Lineage=43e12f0f:5. Quando a função grava esse evento de volta em um serviço suportado usando um SDK suportado, o contador incrementa. Ao chegar a aproximadamente 16 invocações na mesma cadeia, o Lambda descarta a próxima e devolve RecursiveInvocationException. Não precisa ativar tracing do X-Ray e não há cobrança.
Três limites que a página de anúncio não enfatiza:
- Cobertura por serviço. Só Lambda, SQS, SNS e S3. Se o loop passa por DynamoDB Streams, EventBridge, Kinesis ou Step Functions, o contador não viaja e nada é cortado. A própria documentação diz isso em uma frase, merecia um parágrafo.
- Cobertura por SDK. O contador só propaga se a função usa versão mínima do SDK: Node.js 3.105.0 (v3), boto3 1.24.46, Java 2.20.81 no runtime 17, Go v2 1.57.0. Uma função Python que empacota um boto3 antigo no deployment package sai da proteção em silêncio.
- Profundidade, não largura. O contador mede quantas vezes aquele evento invocou a função. Um loop de fan-out, uma invocação que gera três objetos, cada um gerando outra invocação, tem 16 níveis de profundidade, mas cada nível multiplica a largura. Dezesseis saltos com fator 3 são dezenas de milhões de invocações antes do corte. A detecção limita a cadeia; não limita a árvore.
O atraso do aviso: a notificação no Health Dashboard e o e-mail podem levar até 3,5 horas, e o e-mail é no máximo um por função a cada 24 horas. A métrica RecursiveInvocationsDropped sai na hora, é nela que o alarme tem que estar.
Anatomia do loop e as quatro camadas que o cortam
O contador Lineage viaja no evento S3 → Lambda → S3; cada guardrail atua em um ponto diferente da cadeia.
- S3 bucket · ObjectCreated → Lineage=hash:n
- Lambda · SDK ≥ mínimo incrementa n
- SQS fila fonte · maxReceiveCount + DLQ
- 1. Detecção nativa · corta em ~16 saltos
- 2. Separação estrutural · prefixo/sufixo, bucket distinto
- 3. Reserved concurrency · limita a largura
- 4. Contador próprio · cobre DynamoDB/EventBridge
- CloudWatch · RecursiveInvocationsDropped (imediato)
- Health Dashboard + e-mail · até 3,5 h; 1 e-mail/24 h
- DLQ / on-failure destination · evento descartado
Quatro guardrails lado a lado
| Detecção nativa (Terminate) | Separação estrutural | Teto de concorrência + alarmes | Contador de saltos na aplicação | |
|---|---|---|---|---|
| O que corta | A cadeia, em ~16 invocações | O loop nunca nasce | A largura, não a cadeia | A cadeia, no teto que você definir |
| Serviços cobertos | Lambda, SQS, SNS, S3 | Qualquer um, se o desenho permitir | Qualquer um | Qualquer um que carregue metadado |
| Pré-requisito | SDK na versão mínima; partição com suporte | Buckets/filas/prefixos distintos por papel | Reserved concurrency por função + alarme em ConcurrentExecutions | Código: ler/gravar hop em atributo ou tabela de idempotência |
| Tempo até você saber | Métrica imediata; Health/e-mail até 3,5 h | Nunca precisa saber | Alarme em 1-5 min, conforme período | Log/métrica própria, imediato |
| Custo de manter | Zero, mas auditar versão de SDK a cada deploy | Disciplina de IaC e revisão de PR | Revisar tetos quando o tráfego cresce | Código próprio: testar, versionar, corrigir por anos |
Matriz de decisão
Detecção nativa (padrão `Terminate`)
- Gratuita, ligada por padrão, sem código
- Métrica
RecursiveInvocationsDroppedsai na hora - Evento parado vai para DLQ ou on-failure destination
- Não cobre DynamoDB, EventBridge, Kinesis, Step Functions
- 16 saltos de fan-out já são milhões de invocações
- Some em silêncio com SDK abaixo do mínimo
Piso obrigatório, nunca teto
Separação estrutural
- Previne em vez de detectar
- Independe de SDK, partição ou serviço
- Filtro de prefixo/sufixo no S3 é configuração, não código
- Exige disciplina em IaC e revisão
- Não protege contra bug que ignora a convenção
A primeira decisão de desenho
Teto de concorrência + alarmes
- Limita o blast radius financeiro de qualquer loop
- Protege o resto da conta da função defeituosa
- Não corta o loop; só o desacelera
- Teto baixo demais vira throttling em pico legítimo
Obrigatório em toda função com trigger
Contador de saltos na aplicação
- Único que cobre DynamoDB Streams e EventBridge
- Teto configurável: 3, não 16
- Código próprio que você mantém por anos
- Se o metadado se perde no meio, o contador zera
Só quando o loop passa por serviço não coberto
A conta que justifica as camadas
Cenário: função de 512 MB, 200 ms por execução, disparada por ObjectCreated no mesmo bucket em que grava. Sem reserved concurrency, o Lambda escala até a quota da conta: 1.000 de concorrência padrão em região comercial; no EUSC, confirme o valor na sua conta, porque quota lá costuma passar por ticket. Uso preço de lista das regiões comerciais como proxy; o EUSC tem tabela própria e você deve conferir antes de fechar o número.
Sem guardrail nenhum: 1.000 execuções concorrentes de 200 ms são 5.000 invocações por segundo. Cada uma custa cerca de US$ 0,0000019 em compute mais US$ 0,000005 de PutObject no S3, o PUT custa mais que a função. São cerca de US$ 0,035 por segundo, ou US$ 125 por hora. Nas 3,5 horas que o Health Dashboard pode levar para avisar, a conta chega a US$ 440. Em uma noite de sexta, passa de US$ 1.500 antes de alguém abrir o e-mail. Não é catastrófico para um banco; é o suficiente para virar item de auditoria e pergunta do CFO.
Só com detecção nativa: o loop simples morre em 16 invocações, centavos. O loop de fan-out não: 16 níveis com fator 3 são 3^16 ≈ 43 milhões de invocações, e a detecção corta cada cadeia individual só quando ela chega ao nível 16. A conta volta ao cenário anterior, agora limitada pelo tempo até a árvore se esgotar.
Com reserved concurrency de 50: o pior caso cai para 250 invocações por segundo: US$ 6 por hora. As 3,5 horas de atraso custam US$ 22. É esse número que me deixa dormir: a detecção nativa corta a cadeia; a concorrência reservada limita a largura; nenhuma das duas sozinha limita o custo.
A lição: cada guardrail atua em uma dimensão, profundidade, largura ou existência do loop. Empilhar os três não é redundância; é cobrir três eixos diferentes do mesmo problema.
Configuração que eu exijo antes de ligar um trigger
Estado da detecção como código: PutFunctionRecursionConfig aceita Allow ou Terminate, e o padrão é Terminate. Declare explicitamente em SAM ou CloudFormation e coloque um SCP ou uma policy de pipeline negando lambda:PutFunctionRecursionConfig fora do papel de plataforma. Quem precisar de Allow, um crawler que se reenfileira de propósito, justifica em ADR e recebe reserved concurrency baixa como contrapartida. Lembre que com Allow a métrica RecursiveInvocationsDropped não é emitida: você perde o sinal junto com a proteção.
Alarme na métrica certa: RecursiveInvocationsDropped com Statistic: Sum, período de 60 segundos, threshold >= 1, uma avaliação. Não espere o Health Dashboard. Ao lado, ConcurrentExecutions por função com threshold em 80% da reserved concurrency, é o alarme que pega o fan-out e o loop via DynamoDB que a detecção não vê.
SQS como fonte: configure a DLQ na fila fonte, não na função. A DLQ da função vale só para invocação assíncrona. E entenda a interação: após a detecção, o RecursiveInvocationException incrementa o receiveCount a cada retentativa, então a mensagem só vai para a DLQ quando o maxReceiveCount da redrive policy estoura. Um maxReceiveCount de 1.000 mantém a mensagem batendo na porta por horas: 5 é razoável para a maioria dos casos.
S3 como fonte: prefixo de entrada e prefixo de saída distintos, com o filtro de notificação apontando só para o de entrada. Bucket separado quando o compliance permitir; prefixo quando não. Nunca a mesma variável de ambiente para os dois, foi exatamente isso que me derrubou.
SDK: trave a versão mínima no package.json/requirements.txt e faça o pipeline falhar abaixo dela. É a única forma de a proteção não sumir em silêncio.
O loop que a detecção não vê é o mais comum em plataforma de dados
Em arquitetura de dados, o padrão dominante é Lambda lendo DynamoDB Streams e gravando na mesma tabela, enriquecimento, materialização, contadores. Esse loop não passa por SQS, SNS nem S3, então o contador Lineage não viaja e o Lambda não corta nada. Aqui a proteção real é um atributo hop no item mais um filtro de evento no event source mapping (FilterCriteria descartando itens onde hop >= 3). Custa 20 linhas de código e uma condição de filtro; não ter custa uma tabela crescendo em ritmo exponencial até estourar a quota de WCU.
Anti-padrões que vejo em revisão
- Confiar na detecção como único guardrail: ela cobre quatro serviços e corta em 16, o fan-out e o loop via DynamoDB passam inteiros.
Allowno template base: alguém copiou de um exemplo de recursão intencional e toda função nova nasce sem proteção e sem métrica.- DLQ da função no lugar da DLQ da fila: a mensagem envenenada nunca sai da fila fonte e o
receiveCountsobe até omaxReceiveCount, que ninguém revisou. - Alarme no Health Dashboard: 3,5 horas de atraso é tempo de loop, não tempo de resposta. O alarme vai em
RecursiveInvocationsDroppedeConcurrentExecutions. - Assumir paridade no EUSC: o guardrail que existia na partição comercial levou quase três anos para chegar. Verifique cada feature implícita na partição de destino antes de migrar a carga.
Toda função com trigger no meu ambiente nasce com três coisas no template: RecursiveLoop: Terminate explícito, reserved concurrency dimensionada para o pico legítimo mais 30%, e alarme em RecursiveInvocationsDropped >= 1 em 60 segundos. Separação de prefixo no S3 e DLQ na fila fonte são checagem de PR, não confiança em ninguém. A lição dura veio daquela variável de ambiente duplicada: o custo não foi o dinheiro da noite, foi a semana explicando ao compliance por que uma função de conciliação rodou 4 milhões de vezes sem ninguém saber. Guardrail que avisa em 3,5 horas serve para o post-mortem; o que serve para o plantão é o que corta em 60 segundos.
Recomendação
Use a detecção nativa como piso em toda função, em qualquer partição, e nunca como argumento para dispensar as outras camadas. Empilhe separação estrutural (previne), reserved concurrency com alarme em ConcurrentExecutions (limita a largura) e detecção nativa com alarme em RecursiveInvocationsDropped (corta a profundidade) quando: a função é disparada por S3, SQS ou SNS e grava em serviço do mesmo tipo. Acrescente um contador de saltos na aplicação quando: o loop passa por DynamoDB Streams, EventBridge, Kinesis ou Step Functions, a detecção nativa não vê nenhum deles. Aceite Allow só com ADR, reserved concurrency baixa e revisão trimestral. No eusc-de-east-1, some a isso a verificação explícita de que Cost Anomaly Detection e alarme de billing existem na partição, a rede de segurança que você tinha em eu-central-1 não vem junto na migração.
Referências
Deep dives de arquitetura, AWS, IA e mercado: direto no seu email. Grátis.
Sem spam · cancele quando quiser
Pergunte ao Fernando sobre isto
Receba uma resposta focada sobre este artigo do meu assistente de IA, baseada no meu trabalho.
Participe da conversa
Entre para comentar
Confirme seu e-mail para participar, você também recebe a newsletter. Sem senha.
Continue lendo
Inteligência de arquitetura, na sua caixa de entrada
Sinais curados e análises originais sobre AWS, IA, sistemas distribuídos e mercado: do jeito que um arquiteto de soluções lê.
- Curadoria de AWS · IA · arquitetura · mercado
- Novos estudos de arquitetura e deep-dives quando saem
- Sínteses diretas, profundidade sem ruído
- Sem spam · double opt-in · cancele quando quiser