Loop Infinito entre Lambda e S3: Como Evitar o Gatilho Recursivo
Você configura uma função Lambda para processar arquivos enviados a um bucket S3 e salvar o resultado no mesmo bucket — parece razoável até o momento em que a função começa a se chamar recursivamente, consumindo invocações em escala exponencial e gerando uma conta inesperada no final do mês. Esse padrão de loop infinito entre Lambda e S3 é um dos erros operacionais mais comuns em arquiteturas orientadas a eventos na AWS.
TL;DR — Resumo do Problema e Soluções
| Situação | Causa | Solução Recomendada |
|---|---|---|
| Lambda salva no mesmo bucket que dispara o gatilho | Notificação S3 re-aciona a função | Usar bucket separado para saída |
| Bucket único é obrigatório por requisito | Prefixo de saída não filtrado | Filtrar por prefixo de entrada no gatilho |
| Prefixo não resolve (ex: transformação no mesmo prefixo) | Filtro insuficiente | Usar tag de metadado no objeto ou verificação lógica no código |
| Loop já em execução | Invocações acumuladas na fila | Desabilitar o gatilho imediatamente via console ou CLI |
Como o Loop Infinito com Lambda e S3 Acontece
O Amazon S3 suporta notificações de eventos que disparam uma função Lambda quando um objeto é criado no bucket. O problema surge quando a própria função Lambda grava um objeto no mesmo bucket — o S3 interpreta essa gravação como um novo evento e aciona a função novamente. Sem nenhum mecanismo de parada, o ciclo se repete indefinidamente.
objeto no bucket"]) --> S3Evt["S3 Emite Evento
ObjectCreated"] S3Evt --> Lambda["Lambda Executa
processa arquivo"] Lambda --> Grava["Grava resultado
no mesmo bucket"] Grava --> S3Evt2["S3 Emite Evento
ObjectCreated novamente"] S3Evt2 --> Lambda2["Lambda Executa
outra vez"] Lambda2 --> Grava2["Grava resultado
no mesmo bucket"] Grava2 --> Loop(["... loop infinito"]) style Loop fill:#ff4444,color:#fff style S3Evt2 fill:#ff8800,color:#fff style Lambda2 fill:#ff8800,color:#fff
- Upload inicial: um objeto chega ao bucket S3 (ex: prefixo
input/). - Notificação S3: o bucket emite um evento
s3:ObjectCreated:*e aciona a função Lambda. - Processamento: a Lambda lê o arquivo, processa e grava o resultado — no mesmo bucket, sem prefixo diferenciado.
- Re-disparo: a gravação do resultado gera um novo evento
ObjectCreated, acionando a Lambda outra vez. - Loop: o ciclo continua até estourar a cota de concorrência, o limite de conta, ou gerar um custo significativo.
Pense no gatilho S3 como um sensor de movimento na porta de um quarto: se você instala o sensor dentro do quarto e a porta abre para dentro, qualquer movimento dentro do quarto também aciona o sensor — incluindo o movimento causado pela própria abertura da porta.
Solução 1: Buckets Separados para Entrada e Saída (Recomendado)
A abordagem mais limpa e operacionalmente segura é usar dois buckets distintos: um para receber os arquivos de entrada (onde o gatilho está configurado) e outro para armazenar os resultados processados. A Lambda lê do bucket de entrada e escreve no bucket de saída — o gatilho nunca é acionado pela escrita.
Criando os Buckets
# Bucket de entrada — gatilho configurado aqui
aws s3api create-bucket \
--bucket meu-bucket-entrada \
--region us-east-1
# Bucket de saída — Lambda escreve aqui, sem gatilho
aws s3api create-bucket \
--bucket meu-bucket-processados \
--region us-east-1
Note que para us-east-1 (us-east-1 é a região padrão da AWS), o parâmetro --create-bucket-configuration não deve ser utilizado — a AWS retorna erro se você incluir LocationConstraint para essa região específica.
Configurando o Gatilho Apenas no Bucket de Entrada
aws lambda add-permission \
--function-name minha-funcao-processamento \
--statement-id s3-trigger-entrada \
--action lambda:InvokeFunction \
--principal s3.amazonaws.com \
--source-arn arn:aws:s3:::meu-bucket-entrada \
--source-account 123456789012
aws s3api put-bucket-notification-configuration \
--bucket meu-bucket-entrada \
--notification-configuration '{
"LambdaFunctionConfigurations": [
{
"LambdaFunctionArn": "arn:aws:lambda:us-east-1:123456789012:function:minha-funcao-processamento",
"Events": ["s3:ObjectCreated:*"]
}
]
}'
Política IAM Mínima para a Função Lambda
🔽 Clique para expandir a política IAM
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "LerDosBucketEntrada",
"Effect": "Allow",
"Action": ["s3:GetObject"],
"Resource": "arn:aws:s3:::meu-bucket-entrada/*"
},
{
"Sid": "EscreverNoBucketSaida",
"Effect": "Allow",
"Action": ["s3:PutObject"],
"Resource": "arn:aws:s3:::meu-bucket-processados/*"
}
]
}
Restringir a permissão s3:PutObject apenas ao bucket de saída na política IAM adiciona uma camada de proteção: mesmo que o código da função contenha um bug de path, ela não conseguirá escrever no bucket de entrada.
Solução 2: Filtro por Prefixo no Mesmo Bucket
Quando o uso de um único bucket é um requisito não negociável, a segunda opção é configurar o gatilho S3 com filtros de prefixo e sufixo, de forma que apenas objetos no prefixo de entrada acionem a Lambda — e a função grave os resultados em um prefixo diferente, fora do escopo do filtro.
prefixo: input/"} Filtro -->|"corresponde"| Lambda["Lambda Executa"] Lambda --> Saida["Grava: output/arquivo.csv"] Saida --> Filtro2{"Filtro do Gatilho
prefixo: input/"} Filtro2 -->|"não corresponde
— ignorado"| Stop(["Nenhuma invocação"]) style Stop fill:#22aa44,color:#fff style Filtro2 fill:#2255cc,color:#fff
- O gatilho escuta apenas objetos criados sob o prefixo
input/. - A Lambda processa e grava o resultado sob o prefixo
output/. - A criação do objeto em
output/não corresponde ao filtro do gatilho — nenhuma nova invocação ocorre.
Configurando o Gatilho com Filtro de Prefixo
aws s3api put-bucket-notification-configuration \
--bucket meu-bucket-unico \
--notification-configuration '{
"LambdaFunctionConfigurations": [
{
"LambdaFunctionArn": "arn:aws:lambda:us-east-1:123456789012:function:minha-funcao-processamento",
"Events": ["s3:ObjectCreated:*"],
"Filter": {
"Key": {
"FilterRules": [
{
"Name": "prefix",
"Value": "input/"
}
]
}
}
}
]
}'
Um detalhe crítico: o S3 não permite configurar duas notificações com filtros de prefixo que se sobreponham para o mesmo destino. Se você já tiver outra notificação configurada no bucket, verifique se os prefixos são mutuamente exclusivos antes de aplicar a configuração acima.
Solução 3: Verificação Lógica no Código da Função
Esta abordagem funciona como uma camada de defesa adicional, não como solução primária. A ideia é inspecionar os metadados do objeto recebido no evento — como o prefixo da chave ou um metadado customizado — e encerrar a execução imediatamente se o arquivo já foi processado.
import boto3
s3 = boto3.client('s3')
def lambda_handler(event, context):
record = event['Records'][0]
bucket = record['s3']['bucket']['name']
key = record['s3']['object']['key']
# Encerra se o objeto já está no prefixo de saída
if key.startswith('output/'):
print(f'Objeto {key} ignorado — prefixo de saída detectado.')
return
# Processamento real aqui
response = s3.get_object(Bucket=bucket, Key=key)
conteudo = response['Body'].read()
# Salva resultado com prefixo diferente
chave_saida = key.replace('input/', 'output/', 1)
s3.put_object(Bucket=bucket, Key=chave_saida, Body=conteudo)
print(f'Processado: {key} -> {chave_saida}')
Essa verificação no código é útil como proteção secundária, mas não substitui o filtro no gatilho. Se o filtro falhar por uma reconfiguração acidental, a lógica no código ainda evita o loop.
Como Parar um Loop que Já Está em Execução
Se o loop já está ativo, a prioridade é interromper as invocações antes de investigar a causa. A forma mais rápida é desabilitar o mapeamento de origem de eventos (event source mapping) ou remover a permissão do gatilho S3.
Passo 1 — Identificar e Desabilitar o Gatilho
# Listar as configurações de notificação do bucket
aws s3api get-bucket-notification-configuration \
--bucket meu-bucket-problema
# Remover todas as notificações do bucket (para emergência)
aws s3api put-bucket-notification-configuration \
--bucket meu-bucket-problema \
--notification-configuration '{}'
Passo 2 — Verificar Invocações em Andamento
# Verificar a concorrência atual da função
aws lambda get-function-concurrency \
--function-name minha-funcao-processamento
# Definir concorrência reservada como 0 para bloquear novas invocações
aws lambda put-function-concurrency \
--function-name minha-funcao-processamento \
--reserved-concurrent-executions 0
Definir a concorrência reservada como 0 efetivamente throttle todas as invocações da função — elas retornarão erro TooManyRequestsException em vez de executar. Isso dá tempo para limpar os objetos duplicados no bucket e reconfigurar o gatilho corretamente antes de restaurar a concorrência.
Passo 3 — Restaurar a Concorrência Após Correção
# Remover a restrição de concorrência reservada
aws lambda delete-function-concurrency \
--function-name minha-funcao-processamento
Diagnóstico: Sintoma, Diagnóstico Errado e Causa Real
O alerta chega via CloudWatch: a métrica Invocations da função está subindo de forma exponencial, e o custo do Lambda disparou em minutos. O primeiro instinto é verificar se há algum cliente externo chamando a função em loop — você olha os logs do API Gateway, não encontra nada suspeito, e perde tempo investigando a camada errada.
A causa real está nos logs do CloudWatch da própria função: cada invocação registra um key diferente, mas todos seguem o padrão processed-processed-processed-arquivo.csv — o prefixo 'processed-' sendo concatenado a cada ciclo. A função estava renomeando o arquivo com um prefixo fixo, mas o gatilho estava configurado sem filtro de prefixo, então cada arquivo renomeado acionava uma nova invocação.
# Verificar os logs recentes para identificar o padrão das chaves
aws logs filter-log-events \
--log-group-name /aws/lambda/minha-funcao-processamento \
--start-time $(date -d '10 minutes ago' +%s000) \
--filter-pattern '"key"' \
--query 'events[*].message' \
--output text
Se os logs mostrarem chaves com prefixos repetidos ou crescentes, o loop está confirmado. A correção é aplicar o filtro de prefixo no gatilho ou migrar para buckets separados.
Prevenção com Recursive Loop Detection (Proteção Nativa AWS)
A partir de 2023, a AWS introduziu detecção de loop recursivo para Lambda. Quando a AWS detecta que uma função Lambda está sendo invocada recursivamente por um serviço suportado (incluindo S3 via SQS ou SNS como intermediário), ela pode interromper automaticamente as invocações e notificar via CloudWatch. Essa proteção opera como uma rede de segurança — não substitui a arquitetura correta, mas reduz o impacto de um loop acidental.
Para verificar se a proteção está habilitada na sua conta:
aws lambda get-account-settings
Consulte a documentação oficial da AWS sobre 'Recursive loop detection' para verificar quais integrações são cobertas e os comportamentos exatos de interrupção, pois o escopo da proteção pode evoluir com o tempo.
Próximos Passos e Conclusão — Prevenindo o Loop Infinito entre Lambda e S3
O padrão mais seguro para evitar o loop infinito entre Lambda e S3 é sempre separar os buckets de entrada e saída. Filtros de prefixo funcionam, mas dependem de disciplina operacional contínua — qualquer alteração no código que mude o prefixo de saída pode reativar o loop silenciosamente.
- Use buckets separados como padrão arquitetural padrão.
- Adicione verificação de prefixo no código como defesa em profundidade.
- Configure alarmes CloudWatch na métrica
Invocationscom threshold anômalo para detecção precoce. - Documente o prefixo de saída esperado como parte da configuração do gatilho.
Referências oficiais:
- AWS Lambda — Usando Lambda com Amazon S3
- Amazon S3 — Filtragem de notificações por prefixo e sufixo
- AWS Lambda — Detecção de loop recursivo
Glossário
| Termo | Definição |
|---|---|
| Event Source Mapping | Configuração que conecta uma fonte de eventos (como S3 ou SQS) a uma função Lambda, definindo quando a função será invocada. |
| s3:ObjectCreated:* | Tipo de evento S3 que cobre qualquer operação de criação de objeto: PUT, POST, COPY e multipart upload completo. |
| Concorrência Reservada | Limite máximo de execuções simultâneas alocado exclusivamente para uma função Lambda específica. Definir como 0 bloqueia todas as invocações. |
| Filtro de Prefixo | Regra na configuração de notificação S3 que restringe o disparo de eventos apenas a objetos cujas chaves começam com um determinado string. |
| Recursive Loop Detection | Proteção nativa da AWS que detecta e interrompe invocações Lambda em loop recursivo em integrações suportadas. |
Comentários
Postar um comentário