Pular para o conteúdo
fernando.moretes.com
BlogEstudosCursosE-booksOpen SourcePodcasts
Loading…
Fernando Azevedo

Arquiteto de TI Especialista

Arquitetura, AWS, IA em produção, sistemas financeiros e FinOps, escritos a partir do que foi operado, com os números.

  • LinkedIn
  • GitHub
  • E-mail

Conteúdo

  • Blog
  • Estudos de arquitetura
  • Cursos
  • E-books
  • Open Source
  • Podcasts
  • Assuntos
  • Trilhas

Ferramentas

  • Well-Architected self-check
  • Arquitetura deste site
  • Números públicos
  • Histórico de estudo

Sobre

  • Comunidade
  • IA Builder
  • Perfil
  • Currículo (CV)
  • Trabalhe comigo
  • Media kit
  • RSS do blog
  • RSS dos estudos
  • Artigos narrados (podcast)
  • llms.txt
  • Termos, privacidade e uso de IA

(c) 2026 Fernando Francisco Azevedo

Blog/Artigo
RSSMarkdownLLMs
AWS & CloudComparativo

Loop recursivo no Lambda: quatro guardrails comparados no Sovereign Cloud

11 de set. de 2026 7 minAvançado Com apoio de IA000 visualizações00 reproduções
Ouvir artigo

14:58 · voz do Fernando

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:

  1. 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.
  2. 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.
  3. 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.

🟧 AWS EUSC, a cadeia de eventos (eusc-de-east-1)
  • S3 bucket · ObjectCreated → Lineage=hash:n
  • Lambda · SDK ≥ mínimo incrementa n
  • SQS fila fonte · maxReceiveCount + DLQ
🔐 Guardrails, onde cada opção corta
  • 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
📤 Sinais, o que você vê e quando
  • 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 estruturalTeto de concorrência + alarmesContador de saltos na aplicação
O que cortaA cadeia, em ~16 invocaçõesO loop nunca nasceA largura, não a cadeiaA cadeia, no teto que você definir
Serviços cobertosLambda, SQS, SNS, S3Qualquer um, se o desenho permitirQualquer umQualquer um que carregue metadado
Pré-requisitoSDK na versão mínima; partição com suporteBuckets/filas/prefixos distintos por papelReserved concurrency por função + alarme em ConcurrentExecutionsCódigo: ler/gravar hop em atributo ou tabela de idempotência
Tempo até você saberMétrica imediata; Health/e-mail até 3,5 hNunca precisa saberAlarme em 1-5 min, conforme períodoLog/métrica própria, imediato
Custo de manterZero, mas auditar versão de SDK a cada deployDisciplina de IaC e revisão de PRRevisar tetos quando o tráfego cresceCódigo próprio: testar, versionar, corrigir por anos

Matriz de decisão

Detecção nativa (padrão Terminate)

Prós
  • Gratuita, ligada por padrão, sem código
  • Métrica RecursiveInvocationsDropped sai na hora
  • Evento parado vai para DLQ ou on-failure destination
Contras
  • 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

Prós
  • 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
Contras
  • 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

Prós
  • Limita o blast radius financeiro de qualquer loop
  • Protege o resto da conta da função defeituosa
Contras
  • 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

Prós
  • Único que cobre DynamoDB Streams e EventBridge
  • Teto configurável: 3, não 16
Contras
  • 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.
  • Allow no 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 receiveCount sobe até o maxReceiveCount, 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 RecursiveInvocationsDropped e ConcurrentExecutions.
  • 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.
FA
O que eu faço na prática
Arquiteto de TI Especialista

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

Piso obrigatório; nunca guardrail único

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

AWS What's New: Lambda recursive loop detection in Europe Sovereign Cloud (Sep 10, 2026)Lambda Developer Guide: Use Lambda recursive loop detection to prevent infinite loopsLambda Developer Guide (European Sovereign Cloud), recursive loop detectionLambda API Reference: PutFunctionRecursionConfigAWS Compute Blog: Detecting and stopping recursive loops in AWS Lambda functionsAWS What's New: Lambda detects and stops recursive loops between Lambda and Amazon S3 (Oct 2024)Serverless Land: Recursive patterns that cause run-away Lambda functionscloudonaut: AWS European Sovereign Cloud (EUSC): a field report
#AWS Lambda#European Sovereign Cloud#event-driven#FinOps#SQS#S3#guardrails#serverless
Compartilhar:LinkedInWhatsAppXBaixar em Markdown
Fonte analisada: AWS Lambda recursive loop detection is now available in Europe Sovereign Cloud
Próximo passo
Trilha Arquiteto de IA, gratuitaFundamentos, LLMs, RAG, tools, MCP e arquitetura na AWS, em aulas curtas e interativas.Começar a trilhaAgentes de IA em produção na AWSComo eu desenho, oriento e audito agentes que vão ao ar, com os números e as fontes.Ver o IA Builder

Continue lendo

AWS & CloudSnapStart em container: o bake-off contra concorrência provisionadaA AWS liberou SnapStart para funções empacotadas como imagem de container, prometendo partida sub-segundo em artefatos de até 10 GB. Coloquei a novidade lado a lado com concorrência provisionada, com o .zip e com um serviço sempre ligado no Fargate, usando os preços publicados e os limites da documentação. A conclusão não é sobre latência: é sobre onde o seu pipeline passa a pagar aluguel.Ler AWS & CloudStreaming de Respostas com Lambda: Precificação em Tempo RealLambda response streaming muda fundamentalmente o contrato de execução serverless ao permitir que bytes sejam enviados ao cliente antes que a função termine, o que tem implicações profundas para motores de precificação em tempo real. Nesta análise, desmonto o mecanismo interno, os modos de falha que a documentação não enfatiza, e as decisões de arquitetura que separam um protótipo de um sistema financeiro em produção.Ler AWS & CloudRetro: EMR Serverless em IPv6-only e a subnet que ficou sem DNS64O EMR Serverless passou a aceitar subnets dual-stack e IPv6-only em 16/10/2026, sem custo adicional e em todas as release versions. O recurso resolve a falta de IPv4 em subnets compartilhadas, mas transfere para você a prontidão da rede: DNS64, rota NAT64, egress-only gateway e endpoints. Escrevi o retro do incidente que eu esperaria na primeira noite de batch, para que ele não precise acontecer.Ler
AnteriorCapacidade por tipo de trabalho no Amazon Connect: o que muda de fatoPróximoMediaLive sem timecode: o que o Video Aligned Locking muda na redundância
Gostou desta análise? Receba a próxima.

Deep dives de arquitetura, AWS, IA e mercado: direto no seu email. Grátis.

Sem spam · cancele quando quiser

Pergunte ao Fernando sobre isto

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.

Neste artigo
O que o Sovereign Cloud muda na equaçãoComo a detecção nativa funciona, e onde ela paraArquiteturaQuatro guardrails lado a ladoMatriz de decisãoA conta que justifica as camadasConfiguração que eu exijo antes de ligar um triggerAntipadrõesRecomendaçãoReferências