# Loop recursivo no Lambda: quatro guardrails comparados no Sovereign Cloud

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.

- URL: https://fernando.moretes.com/blog/loop-recursivo-no-lambda-quatro-guardrails-comparados-no-sovereign-clo-aws-lambda-r

- Markdown: https://fernando.moretes.com/blog/loop-recursivo-no-lambda-quatro-guardrails-comparados-no-sovereign-clo-aws-lambda-r/article.md?lang=pt

- Published: 2026-09-11T10:14:22.817Z

- Category: IA & Agentes

- Tags: AWS Lambda, European Sovereign Cloud, event-driven, FinOps, SQS, S3, guardrails, serverless

- Reading time: 7 min

- Source: [AWS Lambda recursive loop detection is now available in Europe Sovereign Cloud](https://aws.amazon.com/about-aws/whats-new/2026/09/lambda-recursion-europe-sovereign-cloud)

---

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 (storage)
- Lambda SDK ≥ mínimo incrementa n (compute)
- SQS fila fonte maxReceiveCount + DLQ (messaging)

### 🔐 Guardrails — onde cada opção corta

- 1. Detecção nativa corta em ~16 saltos (security)
- 2. Separação estrutural prefixo/sufixo, bucket distinto (security)
- 3. Reserved concurrency limita a largura (security)
- 4. Contador próprio cobre DynamoDB/EventBridge (security)

### 📤 Sinais — o que você vê e quando

- CloudWatch RecursiveInvocationsDropped (imediato) (data)
- Health Dashboard + e-mail até 3,5 h; 1 e-mail/24 h (external)
- DLQ / on-failure destination evento descartado (messaging)

### Fluxos

- dev -> fn: publica config errada
- s3 -> fn: evento n
- fn -> s3: PutObject → evento n+1
- sqs -> fn: variante SQS
- g2 -> s3: evento nunca nasce
- g4 -> fn: rejeita hop > teto
- g3 -> fn: throttle acima do teto
- g1 -> fn: RecursiveInvocationException
- g1 -> cw: métrica
- g1 -> health: notificação
- g1 -> dlq: evento parado

## Quatro guardrails lado a lado
| Critério | 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`)

**Pros**
- Gratuita, ligada por padrão, sem código
- Métrica `RecursiveInvocationsDropped` sai na hora
- Evento parado vai para DLQ ou on-failure destination

**Cons**
- 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

**Verdict:** Piso obrigatório, nunca teto

### Separação estrutural

**Pros**
- 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

**Cons**
- Exige disciplina em IaC e revisão
- Não protege contra bug que ignora a convenção

**Verdict:** A primeira decisão de desenho

### Teto de concorrência + alarmes

**Pros**
- Limita o blast radius financeiro de qualquer loop
- Protege o resto da conta da função defeituosa

**Cons**
- Não corta o loop; só o desacelera
- Teto baixo demais vira throttling em pico legítimo

**Verdict:** Obrigatório em toda função com trigger

### Contador de saltos na aplicação

**Pros**
- Único que cobre DynamoDB Streams e EventBridge
- Teto configurável — 3, não 16

**Cons**
- Código próprio que você mantém por anos
- Se o metadado se perde no meio, o contador zera

**Verdict:** 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.

> **O que eu faço na prática:** 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.

**Rating:** Piso obrigatório; nunca guardrail único 

## Referências

- [AWS What's New — Lambda recursive loop detection in Europe Sovereign Cloud (Sep 10, 2026)](https://aws.amazon.com/about-aws/whats-new/2026/09/lambda-recursion-europe-sovereign-cloud)
- [Lambda Developer Guide — Use Lambda recursive loop detection to prevent infinite loops](https://docs.aws.amazon.com/lambda/latest/dg/invocation-recursion.html)
- [Lambda Developer Guide (European Sovereign Cloud) — recursive loop detection](https://docs.aws.eu/lambda/latest/dg/invocation-recursion.html)
- [Lambda API Reference — PutFunctionRecursionConfig](https://docs.aws.amazon.com/lambda/latest/api/API_PutFunctionRecursionConfig.html)
- [AWS Compute Blog — Detecting and stopping recursive loops in AWS Lambda functions](https://aws.amazon.com/blogs/compute/detecting-and-stopping-recursive-loops-in-aws-lambda-functions/)
- [AWS What's New — Lambda detects and stops recursive loops between Lambda and Amazon S3 (Oct 2024)](https://aws.amazon.com/about-aws/whats-new/2024/10/aws-lambda-detects-stops-recursive-loops-lambda-s3/)
- [Serverless Land — Recursive patterns that cause run-away Lambda functions](https://serverlessland.com/content/service/lambda/guides/aws-lambda-operator-guide/recursive-runaway)
- [cloudonaut — AWS European Sovereign Cloud (EUSC): a field report](https://cloudonaut.io/aws-european-sovereign-cloud-field-report/)
