Bucle Infinito de Lambda con S3: Cómo Detener la Recursión de Triggers
Tu función Lambda se dispara con cada archivo subido a S3, procesa el contenido y guarda el resultado en el mismo bucket — y de repente el dashboard de Lambda muestra miles de invocaciones en minutos. Este bucle infinito de Lambda con S3 es uno de los errores operacionales más costosos que puedes cometer en producción, y la solución no siempre es obvia cuando estás mirando las métricas dispararse.
TL;DR — Resumen del Problema y Soluciones
| Situación | Causa | Solución Recomendada |
|---|---|---|
| Lambda escribe en el mismo bucket que la dispara | El objeto de salida genera un nuevo evento S3 | Usar buckets separados (input/output) |
| Mismo bucket es inevitable por arquitectura | El prefijo de salida no está excluido del trigger | Filtrar por prefijo/sufijo en la notificación S3 |
| Bucle ya está corriendo en producción | Concurrencia sin límite superior | Establecer reserved concurrency = 0 de inmediato |
Cómo Funciona el Bucle Infinito de Lambda con S3
Cuando configuras una notificación de eventos en un bucket S3 apuntando a una función Lambda, S3 invoca Lambda de forma asíncrona cada vez que ocurre el evento configurado (por ejemplo, s3:ObjectCreated:*). Si tu función escribe un objeto de vuelta al mismo bucket bajo una ruta que también coincide con el filtro del trigger, S3 genera un nuevo evento, que invoca Lambda nuevamente — y así indefinidamente.
en el mismo bucket"] E --> F["S3 genera nuevo evento ObjectCreated"] F --> C style F fill:#ff4444,color:#fff style C fill:#ff8800,color:#fff
- Evento inicial: Un archivo llega al bucket S3 y dispara la notificación configurada.
- Invocación Lambda: S3 invoca la función Lambda de forma asíncrona con los metadatos del objeto.
- Procesamiento y escritura: Lambda procesa el archivo y escribe el resultado en el mismo bucket.
- Nuevo evento generado: El objeto de salida coincide con el filtro del trigger y genera un nuevo evento
s3:ObjectCreated. - Bucle: El ciclo se repite sin condición de parada, consumiendo concurrencia y generando costos.
El problema se amplifica porque las invocaciones asíncronas de Lambda tienen su propia cola interna. Cuando el bucle se establece, puedes tener decenas de invocaciones en vuelo simultáneamente antes de que notes el problema en CloudWatch.
Solución 1: Separar Buckets de Entrada y Salida (Recomendada)
La arquitectura más limpia y la que elimina el riesgo de raíz: el trigger apunta al bucket de entrada, Lambda escribe en un bucket de salida diferente. El bucket de salida no tiene ninguna notificación configurada hacia Lambda.
mi-bucket-input"] B -->|"s3:ObjectCreated"| C["Lambda
mi-funcion-procesadora"] C -->|"s3:PutObject"| D["Bucket OUTPUT
mi-bucket-output"] D --> E["Archivo procesado"] style B fill:#2196F3,color:#fff style D fill:#4CAF50,color:#fff style C fill:#FF9800,color:#fff
- Bucket de entrada: Recibe los archivos originales. Tiene la notificación S3 configurada hacia Lambda.
- Lambda: Lee del bucket de entrada, procesa, escribe en el bucket de salida.
- Bucket de salida: Almacena los resultados procesados. Sin notificaciones hacia Lambda.
- Sin bucle posible: La escritura en el bucket de salida no puede disparar la misma función.
Crear los Buckets y Configurar el Trigger
# Crear bucket de entrada (us-east-1 no requiere --create-bucket-configuration)
aws s3api create-bucket \
--bucket mi-bucket-input \
--region us-east-1
# Crear bucket de salida
aws s3api create-bucket \
--bucket mi-bucket-output \
--region us-east-1
# Verificar que el bucket de salida NO tiene notificaciones configuradas
aws s3api get-bucket-notification-configuration \
--bucket mi-bucket-output
Para configurar la notificación S3 en el bucket de entrada apuntando a tu función Lambda, necesitas primero otorgar permiso a S3 para invocar la función:
aws lambda add-permission \
--function-name mi-funcion-procesadora \
--statement-id s3-invoke-permission \
--action lambda:InvokeFunction \
--principal s3.amazonaws.com \
--source-arn arn:aws:s3:::mi-bucket-input \
--source-account 123456789012
Luego aplica la configuración de notificación al bucket de entrada:
🔽 Expandir: notification-config.json y comando aws s3api
# notification-config.json
{
"LambdaFunctionConfigurations": [
{
"LambdaFunctionArn": "arn:aws:lambda:us-east-1:123456789012:function:mi-funcion-procesadora",
"Events": ["s3:ObjectCreated:*"]
}
]
}
# Aplicar la configuración
aws s3api put-bucket-notification-configuration \
--bucket mi-bucket-input \
--notification-configuration file://notification-config.json
Solución 2: Filtrar por Prefijo o Sufijo en el Trigger de S3
Cuando la separación de buckets no es viable — por ejemplo, cuando la arquitectura existente ya está construida alrededor de un único bucket — puedes usar los filtros de notificación de S3 para que el trigger solo se active con objetos bajo un prefijo específico, y escribir los resultados bajo un prefijo diferente que quede fuera del filtro.
Piénsalo como un portero que solo deja pasar a quienes llegan por la puerta principal. Si tu función de procesamiento siempre escribe en
processed/, y el trigger solo escucha eventos enraw/, el portero nunca ve los archivos de salida.
prefijo processed/"| E["Evento ignorado"] style E fill:#9E9E9E,color:#fff style C fill:#FF9800,color:#fff
- Prefijo
raw/: Solo los objetos bajo este prefijo disparan el evento Lambda. - Lambda procesa el archivo de entrada y escribe el resultado bajo
processed/. - El filtro excluye
processed/: S3 no genera notificación para esos objetos. - Sin recursión: El ciclo se rompe por diseño del filtro.
🔽 Expandir: notification-config con filtro de prefijo
# notification-config-filtered.json
{
"LambdaFunctionConfigurations": [
{
"LambdaFunctionArn": "arn:aws:lambda:us-east-1:123456789012:function:mi-funcion-procesadora",
"Events": ["s3:ObjectCreated:*"],
"Filter": {
"Key": {
"FilterRules": [
{
"Name": "prefix",
"Value": "raw/"
}
]
}
}
}
]
}
# Aplicar la configuración con filtro
aws s3api put-bucket-notification-configuration \
--bucket mi-bucket-unico \
--notification-configuration file://notification-config-filtered.json
# Verificar la configuración aplicada
aws s3api get-bucket-notification-configuration \
--bucket mi-bucket-unico
Una advertencia importante sobre este enfoque: los filtros de prefijo en las notificaciones S3 son exactos y sensibles a mayúsculas. Si tu función escribe en Processed/ en lugar de processed/, el filtro no protege contra el bucle. Verifica el comportamiento de escritura de tu código antes de confiar en esta solución.
Parada de Emergencia: Detener el Bucle en Producción
Si el bucle ya está corriendo y necesitas detenerlo ahora mismo, el mecanismo más rápido es establecer la concurrencia reservada de la función a cero. Esto bloquea todas las invocaciones nuevas de forma inmediata sin eliminar la función ni modificar la configuración del bucket.
# PARADA DE EMERGENCIA — establece concurrencia reservada a 0
aws lambda put-function-concurrency \
--function-name mi-funcion-procesadora \
--reserved-concurrent-executions 0
# Verificar que la concurrencia quedó en 0
aws lambda get-function-concurrency \
--function-name mi-funcion-procesadora
Con concurrencia reservada en 0, S3 seguirá intentando invocar Lambda y recibirá errores de throttling. Esos eventos fallidos se acumularán en la cola de reintentos asíncronos de Lambda. Una vez que corrijas la configuración del trigger y restaures la concurrencia, Lambda procesará los eventos pendientes — lo que puede reiniciar el bucle si no corriges la causa raíz primero.
# Después de corregir la configuración del trigger, restaurar concurrencia
aws lambda delete-function-concurrency \
--function-name mi-funcion-procesadora
Usa delete-function-concurrency para volver al comportamiento de concurrencia no reservada, no put-function-concurrency con un número alto — de lo contrario podrías seguir limitando la función sin darte cuenta.
Diagnóstico: Confirmar que el Bucle Está Activo
Antes de aplicar cualquier solución, confirma que efectivamente tienes un bucle recursivo y no otro problema. El síntoma más claro es una curva de invocaciones en CloudWatch que crece exponencialmente desde un punto en el tiempo específico.
# Ver invocaciones recientes de la función
aws cloudwatch get-metric-statistics \
--namespace AWS/Lambda \
--metric-name Invocations \
--dimensions Name=FunctionName,Value=mi-funcion-procesadora \
--start-time 2024-01-15T00:00:00Z \
--end-time 2024-01-15T01:00:00Z \
--period 60 \
--statistics Sum
# Revisar la configuración actual del trigger en el bucket
aws s3api get-bucket-notification-configuration \
--bucket mi-bucket-unico
Si el output de get-bucket-notification-configuration muestra un LambdaFunctionConfiguration sin filtros de prefijo, y tu función escribe en el mismo bucket, tienes el bucle confirmado.
El Error que Nadie Documenta: La Trampa del Sufijo
Aquí está el patrón de fallo que aparece con más frecuencia en producción: el equipo configura un filtro de sufijo pensando que es suficiente protección.
La lógica parece razonable: 'el archivo de entrada es .csv, el archivo de salida es .json, así que filtro por .csv y listo'. El trigger queda configurado con suffix: .csv. Lambda procesa el CSV y escribe un JSON. Sin bucle, ¿verdad?
Funciona perfectamente — hasta que alguien modifica la función para que también genere un archivo de log en formato .csv para auditoría. Ese archivo de log dispara el trigger. Lambda intenta procesar el log como si fuera un archivo de datos, falla, pero en el proceso de manejo del error escribe otro archivo. Dependiendo del código de manejo de errores, puede o no generar otro .csv.
La causa real no es el filtro de sufijo en sí — es que el contrato entre 'qué escribe Lambda' y 'qué escucha el trigger' no está explícitamente documentado ni validado. El filtro de prefijo con rutas dedicadas (raw/ vs processed/) es más robusto porque el prefijo de escritura está controlado por la función misma, no por el formato del archivo.
IAM: Política Mínima para el Rol de Ejecución de Lambda
Si estás reconstruyendo la arquitectura con buckets separados, el rol de ejecución de Lambda necesita permisos de lectura en el bucket de entrada y escritura en el bucket de salida — y nada más.
🔽 Expandir: política IAM de mínimo privilegio
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "LeerDesdeInputBucket",
"Effect": "Allow",
"Action": [
"s3:GetObject"
],
"Resource": "arn:aws:s3:::mi-bucket-input/*"
},
{
"Sid": "EscribirEnOutputBucket",
"Effect": "Allow",
"Action": [
"s3:PutObject"
],
"Resource": "arn:aws:s3:::mi-bucket-output/*"
}
]
}
Nótese que el rol no tiene s3:PutObject sobre el bucket de entrada. Esto añade una capa de defensa en profundidad: incluso si por algún error de código la función intenta escribir en el bucket de entrada, el permiso IAM lo bloqueará. No es un sustituto de la arquitectura correcta, pero sí un control compensatorio válido.
Verificación Final: Confirmar que el Bucle No Puede Reproducirse
Una vez aplicada la solución, verifica el estado completo antes de restaurar la concurrencia o subir archivos de prueba.
# 1. Confirmar configuración de notificación del bucket de entrada
aws s3api get-bucket-notification-configuration \
--bucket mi-bucket-input
# 2. Confirmar que el bucket de salida NO tiene notificaciones hacia Lambda
aws s3api get-bucket-notification-configuration \
--bucket mi-bucket-output
# 3. Confirmar permisos del rol de ejecución de Lambda
aws iam get-role-policy \
--role-name mi-rol-lambda \
--policy-name LambdaS3Policy
# 4. Subir un archivo de prueba al bucket de entrada y monitorear invocaciones
aws s3 cp archivo-prueba.csv s3://mi-bucket-input/raw/archivo-prueba.csv
# 5. Verificar que solo hubo UNA invocación
aws cloudwatch get-metric-statistics \
--namespace AWS/Lambda \
--metric-name Invocations \
--dimensions Name=FunctionName,Value=mi-funcion-procesadora \
--start-time 2024-01-15T10:00:00Z \
--end-time 2024-01-15T10:05:00Z \
--period 300 \
--statistics Sum
Próximos Pasos y Recursos para el Bucle Infinito de Lambda con S3
La solución de buckets separados es la arquitectura correcta para la mayoría de los casos. Si tu caso requiere un único bucket, los filtros de prefijo son la segunda opción más robusta — pero documenta explícitamente el contrato de rutas de escritura en el código de la función para que futuros cambios no rompan el filtro silenciosamente.
- Configura una alarma de CloudWatch sobre la métrica
Invocationsde tu función con un umbral que detecte crecimientos anómalos antes de que el costo se dispare. - Considera usar concurrencia reservada como límite de seguridad permanente en funciones disparadas por S3, no solo como herramienta de emergencia.
- Revisa la documentación oficial de filtros de notificación S3 para entender las limitaciones de los filtros de prefijo y sufijo.
Glosario de Términos Clave
| Término | Definición |
|---|---|
| Invocación asíncrona | Modo en que S3 llama a Lambda: S3 encola el evento y no espera respuesta. Lambda tiene su propia cola de reintentos para estos eventos. |
| Notificación de bucket S3 | Configuración que indica a S3 qué eventos (ObjectCreated, ObjectRemoved, etc.) deben enviarse a qué destino (Lambda, SNS, SQS). |
| Concurrencia reservada | Límite máximo de ejecuciones simultáneas asignado a una función Lambda específica. Establecerlo en 0 bloquea todas las invocaciones. |
| Filtro de prefijo/sufijo | Regla en la configuración de notificación S3 que restringe qué objetos generan eventos, basándose en el inicio o final de la clave del objeto. |
| Throttling | Rechazo de invocaciones Lambda cuando se supera el límite de concurrencia. Los eventos rechazados en modo asíncrono se reintentarán automáticamente. |
Comentarios
Publicar un comentario