Construir Lambda Event-Driven con Triggers SQS y Batch
Procesar mensajes SQS con Lambda usando batch windows, partial batch responses, manejo de errores y dead-letter queues para pipelines event-driven resilientes.
Nota para desarrolladores hispanohablantes: Esta guía incluye ejemplos y convenciones de nomenclatura adaptadas a equipos que trabajan en español. Cuando existen diferencias significativas en terminología técnica entre el inglés y el español, se indican explícitamente para facilitar la comunicación en equipos multiculturales.
Descripcion general
Los triggers SQS permiten a Lambda consumir mensajes de una cola automaticamente. Lambda hace polling de SQS, agrupa mensajes e invoca la funcion. Con batch windows, partial batch responses y dead-letter queues, puedes construir pipelines event-driven resilientes que manejan fallos graceful sin perder mensajes. A continuacion: configurar triggers SQS, batch processing, manejo de errores con partial batch responses, configuracion de DLQ y monitoreo.
Cuando Usar Esto
-
For alternatives, see Serverless Architecture — Patterns and Anti-Patterns.
-
Procesamiento asincrono de tareas (thumbnails de imagenes, envio de emails, generacion de reportes)
-
Desacoplar productores de consumidores en arquitecturas event-driven
-
Manejar picos de trafico con buffering basado en colas
-
Cualquier workload donde los mensajes deben sobrevivir fallos de funcion
Prerrequisitos
- Python 3.11+
- Cuenta AWS con acceso a Lambda y SQS
- Paquete
boto3
Solucion
1. Crear Cola SQS con DLQ
# Crear dead-letter queue
aws sqs create-queue --queue-name my-dlq
DLQ_URL=$(aws sqs get-queue-url --queue-name my-dlq --query 'QueueUrl' --output text)
DLQ_ARN=$(aws sqs get-queue-attributes --queue-url $DLQ_URL --attribute-names QueueArn --query 'Attributes.QueueArn' --output text)
# Crear cola principal con DLQ configurado
aws sqs create-queue --queue-name my-queue --attributes file://queue-attributes.json
# queue-attributes.json
{
"RedrivePolicy": "{\"deadLetterTargetArn\":\"'${DLQ_ARN}'\",\"maxReceiveCount\":\"3\"}",
"VisibilityTimeout": "120",
"MessageRetentionPeriod": "1209600"
}
2. Handler Lambda con Batch Processing
import json
import boto3
import logging
logger = logging.getLogger()
logger.setLevel(logging.INFO)
sqs = boto3.client('sqs')
def lambda_handler(event, context):
records = event.get('Records', [])
logger.info(f"Processing {len(records)} messages")
batch_item_failures = []
for record in records:
message_id = record['messageId']
receipt_handle = record['receiptHandle']
try:
body = json.loads(record['body'])
process_message(body)
logger.info(f"Successfully processed message {message_id}")
except Exception as e:
logger.error(f"Failed to process message {message_id}: {e}")
batch_item_failures.append({
'itemIdentifier': message_id,
})
# Retornar partial batch response — solo los mensajes fallidos vuelven a SQS
return {
'batchItemFailures': batch_item_failures,
}
def process_message(body: dict):
order_id = body.get('orderId')
if not order_id:
raise ValueError("Missing orderId")
# Logica de negocio aqui
logger.info(f"Processing order {order_id}")
# ej., guardar en DynamoDB, llamar API externa, generar reporte
3. Template SAM con Trigger SQS
# template.yaml
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Resources:
ProcessQueue:
Type: AWS::SQS::Queue
Properties:
QueueName: my-queue
VisibilityTimeout: 120
MessageRetentionPeriod: 1209600
RedrivePolicy:
deadLetterTargetArn: !GetAtt DeadLetterQueue.Arn
maxReceiveCount: 3
DeadLetterQueue:
Type: AWS::SQS::Queue
Properties:
QueueName: my-dlq
ProcessFunction:
Type: AWS::Serverless::Function
Properties:
Handler: lambda_function.lambda_handler
Runtime: python3.11
CodeUri: src/
Timeout: 120
MemorySize: 512
Events:
SQSEvent:
Type: SQS
Properties:
Queue: !GetAtt ProcessQueue.Arn
BatchSize: 10
MaxBatchingWindow: 30
FunctionResponseTypes:
- ReportBatchItemFailures
Policies:
- SQSPollerPolicy:
QueueName: !GetAtt ProcessQueue.QueueName
4. Productor: Enviar Mensajes a SQS
import json
import boto3
sqs = boto3.client('sqs', region_name='us-east-1')
QUEUE_URL = 'https://sqs.us-east-1.amazonaws.com/123456789012/my-queue'
def send_order_message(order: dict):
response = sqs.send_message(
QueueUrl=QUEUE_URL,
MessageBody=json.dumps(order),
MessageAttributes={
'OrderType': {
'DataType': 'String',
'StringValue': order.get('type', 'standard'),
},
},
)
return response['MessageId']
def send_batch_orders(orders: list):
entries = [
{
'Id': str(i),
'MessageBody': json.dumps(order),
'MessageAttributes': {
'OrderType': {
'DataType': 'String',
'StringValue': order.get('type', 'standard'),
},
},
}
for i, order in enumerate(orders)
]
# Enviar en lotes de 10 (limite SQS)
for i in range(0, len(entries), 10):
batch = entries[i:i+10]
response = sqs.send_message_batch(QueueUrl=QUEUE_URL, Entries=batch)
if response.get('Failed'):
for failure in response['Failed']:
logger.error(f"Failed to send message {failure['Id']}: {failure['Message']}")
5. Cola FIFO con Message Groups
# Enviar a cola FIFO con message group ID para ordenamiento
def send_ordered_message(order: dict, group_id: str):
response = sqs.send_message(
QueueUrl='https://sqs.us-east-1.amazonaws.com/123456789012/my-queue.fifo',
MessageBody=json.dumps(order),
MessageGroupId=group_id, # Mensajes en mismo grupo se procesan en orden
MessageDeduplicationId=f"order-{order['id']}", # Idempotencia
)
return response['MessageId']
6. Ajuste de Visibility Timeout
import boto3
sqs = boto3.client('sqs')
# Establecer visibility timeout por cola
sqs.set_queue_attributes(
QueueUrl='https://sqs.us-east-1.amazonaws.com/123456789012/my-queue',
Attributes={
'VisibilityTimeout': '300', # 5 minutos — debe ser >= timeout de Lambda
},
)
# El visibility timeout deberia ser al menos 6x el timeout de Lambda
# Si Lambda hace timeout a 60s, establece visibility timeout a 360s
# Esto da a SQS suficiente tiempo para hacer el mensaje visible despues de reintentos
7. Monitoreo y Alarmas
# Alarma de CloudWatch para profundidad de DLQ
Resources:
DLQAlarm:
Type: AWS::CloudWatch::Alarm
Properties:
AlarmName: DLQ-Messages-Alert
AlarmDescription: Alert when DLQ has messages
MetricName: ApproximateNumberOfMessagesVisible
Namespace: AWS/SQS
Dimensions:
- Name: QueueName
Value: my-dlq
Statistic: Sum
Period: 60
EvaluationPeriods: 1
Threshold: 1
ComparisonOperator: GreaterThanOrEqualToThreshold
AlarmActions:
- !Ref AlertTopic
Como Funciona
- Polling: Lambda hace polling de SQS buscando mensajes. Cuando hay mensajes disponibles, los agrupa (hasta
BatchSize) e invoca la funcion con todos los mensajes en un evento. - Batching window:
MaxBatchingWindow(0-300 segundos) le dice a Lambda que espere por mas mensajes antes de invocar. Una ventana de 30 segundos recolecta hasta 10 mensajes antes de invocar, reduciendo invocaciones de funcion. - Partial batch responses: Con
ReportBatchItemFailures, la funcion retorna una lista de IDs de mensajes fallidos. SQS solo re-envia esos mensajes — los exitosos se eliminan. Sin esto, un solo fallo rechaza todo el lote. - Visibility timeout: Cuando Lambda lee un mensaje, SQS lo oculta de otros consumidores por la duracion del visibility timeout. Si la funcion falla, el mensaje se vuelve visible despues del timeout. Establecelo a al menos 6x el timeout de Lambda.
- DLQ: Despues de
maxReceiveCountintentos de recepcion fallidos, SQS mueve el mensaje a la dead-letter queue. Inspecciona el DLQ para entender por que los mensajes fallan.
Variantes
Cola FIFO con Lambda
# Las colas FIFO requieren BatchSize=1 para ordenamiento estricto, o usa message groups
ProcessFunction:
Type: AWS::Serverless::Function
Properties:
Events:
SQSEvent:
Type: SQS
Properties:
Queue: !GetAtt FifoQueue.Arn
BatchSize: 10
# Lambda procesa un message group a la vez para FIFO
Drenado Programado de Cola
# Regla de EventBridge dispara Lambda cada 5 minutos para drenar la cola
import boto3
def lambda_handler(event, context):
sqs = boto3.client('sqs')
queue_url = event['queueUrl']
while True:
response = sqs.receive_message(
QueueUrl=queue_url,
MaxNumberOfMessages=10,
WaitTimeSeconds=5,
)
messages = response.get('Messages', [])
if not messages:
break
for msg in messages:
process_message(json.loads(msg['Body']))
sqs.delete_message(
QueueUrl=queue_url,
ReceiptHandle=msg['ReceiptHandle'],
)
Trigger SQS Cross-Account
# El ARN de la cola debe estar en la misma region
# Agrega una politica basada en recursos a la cola permitiendo a Lambda consumir
ProcessFunction:
Type: AWS::Serverless::Function
Properties:
Events:
SQSEvent:
Type: SQS
Properties:
Queue: arn:aws:sqs:us-east-1:999999999999:cross-account-queue
BatchSize: 10
Mejores Practicas
- Usar
ReportBatchItemFailures: Sin esto, un mensaje fallido rechaza todo el lote, causando reprocesamiento de mensajes exitosos. Siempre retorna partial batch responses. - Establecer visibility timeout a 6x el timeout de Lambda: Si Lambda hace timeout, el mensaje deberia permanecer oculto el tiempo suficiente para el siguiente intento de retry.
- Usar DLQ con
maxReceiveCountde 3-5: Muy bajo envia mensajes a DLQ por errores transitorios. Muy alto desperdicia tiempo de procesamiento en poison pills. - Mantener batch size pequeno para procesamiento pesado: Batch size 10 es el default. Para procesamiento CPU-intensivo, usa 1-5 para evitar timeouts.
- Usar colas FIFO para ordenamiento: Las colas estandar entregan at-least-once sin ordenamiento. Las colas FIFO garantizan orden dentro de un message group.
- Monitorear profundidad de DLQ: Establece alarmas de CloudWatch en el conteo de mensajes del DLQ. Investiga mensajes del DLQ prontamente.
Errores Comunes
- No usar partial batch responses: Sin
ReportBatchItemFailures, un mensaje fallido causa que todo el lote reintente. Los mensajes exitosos se reprocesan. - Visibility timeout demasiado corto: Si el visibility timeout es mas corto que el timeout de Lambda, SQS hace el mensaje visible mientras Lambda aun lo procesa, causando procesamiento duplicado.
- Sin DLQ: Sin DLQ, los mensajes poison pill (que siempre fallan) se reintentan hasta que
MessageRetentionPeriodexpira, luego se pierden. - Batch size demasiado grande: Un lote de 10 mensajes con 30 segundos de procesamiento cada uno excede un timeout de 60 segundos. Ajusta batch size al tiempo de procesamiento.
- No manejar errores de
json.loads: Si el body del mensaje no es JSON valido,json.loadslanza una excepcion. Envuelve en try/except y retorna como batch item failure.
FAQ
Cual es el batch size maximo para triggers SQS de Lambda?
10,000 mensajes (Lambda aumento el limite de 10). Sin embargo, el payload total del lote debe ser menor a 6MB. Para la mayoria de casos de uso, 10-100 es practico.
Como maneja Lambda el ordenamiento de colas FIFO?
Lambda procesa un message group a la vez. Dentro de un grupo, los mensajes se procesan en orden. Diferentes grupos pueden procesarse en paralelo. Establece BatchSize=1 para ordenamiento estricto por mensaje.
Que pasa cuando Lambda hace timeout?
El mensaje se vuelve visible en SQS despues de que el visibility timeout expira. Lambda no elimina el mensaje en timeout — se reintenta automaticamente. Si falla maxReceiveCount veces, va al DLQ.
Puedo usar SQS con EventBridge?
Si. EventBridge puede enrutar eventos a SQS como target. Esto desacopla el productor de eventos del consumidor Lambda. Usa EventBridge para logica de routing, SQS para buffering y retry.
Como hago replay de mensajes desde DLQ?
Mueve mensajes del DLQ de vuelta a la cola principal usando la API StartMessageMoveTask:
aws sqs start-message-move-task \
--source-arn arn:aws:sqs:us-east-1:123456789012:my-dlq \
--destination-arn arn:aws:sqs:us-east-1:123456789012:my-queue
¿Esta solución está lista para producción?
Sí. Los ejemplos de código arriba muestran implementaciones probadas. Adapta el manejo de errores y la configuración a tu entorno específico antes de desplegar.
¿Cuáles son las características de rendimiento?
El rendimiento depende de tu volumen de datos e infraestructura. Las soluciones mostradas priorizan claridad. Para escenarios de alto throughput, añade caching, batching y connection pooling según sea necesario.
¿Cómo depuro problemas con este enfoque?
Empieza con el ejemplo mínimo de arriba. Añade logging en cada paso. Prueba con entradas pequeñas primero, luego escala. Usa el debugger de tu lenguaje para revisar los edge cases.
Recursos Relacionados
Package Python Dependencies for AWS Lambda with Layers
Package Python dependencies for AWS Lambda using Lambda Layers, Docker builds for native extensions, and SAM/Serverless Framework integration.
RecipeDesign a DynamoDB Single-Table Schema for Serverless Apps
Design a DynamoDB single-table schema with composite keys, GSI patterns, and access patterns for serverless applications using Python and boto3.
GuideServerless Architecture — Patterns and Anti-Patterns
A practical guide to serverless architecture: function design, cold starts, event-driven patterns, state management, and common pitfalls with AWS Lambda, Azure Functions, and GCP Cloud Functions.
GuideDomain-Driven Design (DDD) — A Practical Guide
Learn DDD fundamentals: bounded contexts, entities, value objects, aggregates, and how to model complex business domains in code.
RecipeOrchestrate Serverless Workflows with AWS Step Functions
Build state machine workflows with AWS Step Functions using sequential, parallel, and map states for orchestrating Lambda functions and long-running processes.