Blog de Amazon Web Services (AWS)

Amazon EventBridge Scheduler: procesos batch sin infraestructura que administrar

Procesos nocturnos: el desafío operativo

Las empresas suelen ejecutar procesos batch programados durante la noche. Algunos ejemplos son generar estados de cuenta, enviar correos masivos, consolidar datos o hacer el cierre contable. En el modelo tradicional, estos procesos corren como demonios (procesos en segundo plano) en instancias que administra el equipo de tecnologías de la información (IT).

Este modelo tiene tres características técnicas que complican la operación:

  • El demonio depende de un servidor que alguien debe mantener. El equipo aplica actualizaciones de sistema operativo y parches de seguridad, monitorea la disponibilidad y reinicia procesos colgados.
  • Una ejecución fallida no genera una señal por defecto. Sin instrumentación adicional, no queda registro de cuándo falló el proceso, cuánto tardó ni cuántos registros procesó.
  • La detección depende de un canal externo. En la práctica, el equipo se entera de la falla cuando los usuarios reportan que no recibieron su estado de cuenta.

Considera un escenario representativo. Una institución financiera ejecuta cada noche un demonio que genera y envía los estados de cuenta de sus clientes. Una madrugada, el servidor se reinicia después de aplicar un parche del sistema operativo, y el demonio no vuelve a iniciar. El proceso no registra ningún error porque nunca llega a ejecutarse. A la mañana siguiente, el área de atención al cliente recibe las primeras consultas de usuarios que no recibieron su documento. Pasan cerca de 36 horas desde la ejecución omitida hasta que el equipo identifica la causa, reinicia el demonio y reenvía los estados de cuenta.

[AUTHOR: verifica o reemplaza los detalles del escenario (tipo de proceso, causa y tiempo de detección) con datos de un caso real anonimizado, si lo tienes]

Todo ese esfuerzo operativo sostiene un proceso que, técnicamente, solo necesita ejecutarse a cierta hora con un payload definido. Administrar esos servidores no agrega valor diferencial al negocio.

Amazon EventBridge Scheduler, combinado con destinos serverless como AWS Lambda, elimina la necesidad de administrar servidores para estos procesos.


El problema real con los demonios en servidores

El proceso batch puede correr en una instancia de Amazon Elastic Compute Cloud (Amazon EC2) o en un servidor físico administrado por IT. En ambos casos, el equipo hereda responsabilidades que un servicio administrado puede asumir.

Un servidor disponible no garantiza un proceso disponible. El servidor puede seguir activo aunque el demonio lleve horas detenido. Supervisores de procesos como systemd ayudan a reiniciar demonios caídos, pero configurarlos requiere trabajo adicional y agrega complejidad operativa.

La visibilidad sobre cuándo ocurrió el fallo puede ser limitada en arquitecturas tradicionales. Para saber si el proceso terminó correctamente, cuánto tardó o si procesó el volumen esperado, necesitas instrumentación adicional. Los equipos frecuentemente omiten esa instrumentación en etapas tempranas.

Sin esa instrumentación, el equipo no se entera de las fallas hasta que los usuarios se quejan. En otras palabras, el ciclo de detección de errores pasa por el cliente, no por el sistema.

Escalar este modelo tampoco funciona bien. Si necesitas agregar un nuevo proceso programado, tienes que coordinar con IT para provisionar o reutilizar infraestructura. El ciclo de aprobaciones puede tomar días.


Amazon EventBridge Scheduler como solución

Amazon EventBridge Scheduler es un servicio totalmente administrado de AWS para crear, ejecutar y administrar tareas programadas a escala. No requiere instancias, no requiere demonios y no requiere que nadie recuerde reiniciar un proceso después de un despliegue.

La arquitectura es directa: en el horario definido, Amazon EventBridge Scheduler invoca el destino y le envía el payload configurado. Ese destino puede ser AWS Lambda, Amazon Simple Queue Service (Amazon SQS) o AWS Step Functions. Mediante destinos universales, también puede ser cualquiera de más de 270 servicios adicionales de AWS.

Para la mayoría de los procesos batch nocturnos, el patrón más común es:

Amazon EventBridge Scheduler → AWS Lambda → lógica de negocio

Con AWS Lambda, no administras servidores. Con Amazon EventBridge Scheduler, no administras el disparador. El equipo solo escribe la lógica de negocio.

Antes de elegir AWS Lambda, mide cuánto dura tu proceso. Una función de AWS Lambda se ejecuta durante un máximo de 15 minutos por invocación. Si tu proceso batch tarda más, por ejemplo al generar miles de estados de cuenta en una sola corrida, cambia el destino del scheduler por una de estas alternativas:

  • AWS Step Functions: divide el proceso en pasos y coordina varias invocaciones de AWS Lambda. Cada paso respeta el límite de 15 minutos, y un flujo de trabajo estándar puede durar hasta un año.
  • AWS Batch: ejecuta trabajos de larga duración en contenedores y administra la capacidad de cómputo por ti.
  • Tareas de Amazon Elastic Container Service (Amazon ECS) en AWS Fargate: ejecuta tu proceso en un contenedor sin límite de duración y sin administrar servidores.

Con cualquiera de estos destinos, Amazon EventBridge Scheduler sigue controlando el horario, los reintentos y la cola de mensajes no procesados de la misma forma.

comparación entre on-prem vs en la nube

Comparación entre on-prem vs AWS


Características que resuelven el problema operativo

Amazon EventBridge Scheduler ofrece tres tipos de schedules que cubren la mayoría de los casos de uso batch: cron, rate y one-time.

Los schedules de tipo cron aceptan expresiones cron de seis campos: minutos, horas, día del mes, mes, día de la semana y año. También permiten configurar la zona horaria. Esto resuelve uno de los problemas clásicos de cron en servidores: los horarios en Tiempo Universal Coordinado (UTC), que debes convertir manualmente a la zona horaria local.

Los schedules de tipo rate ejecutan el destino a intervalos fijos, con expresiones como rate(1 hour) o rate(15 minutes). Este tipo funciona bien para procesos que corren con frecuencia constante sin depender de una hora específica del día, como sincronizar datos cada hora.

Los schedules de una sola vez (one-time) programan ejecuciones futuras sin mantener un proceso esperando. Esto resulta útil para operaciones diferidas, como procesar un archivo que llegará en una fecha específica.

Sobre cualquiera de estos tres tipos, puedes configurar una ventana de tiempo flexible (Flexible Time Window). Esta opción distribuye la ejecución dentro de un rango de tiempo en lugar de dispararla en el minuto exacto. Imagina que tienes 500 procesos programados exactamente a la medianoche. Con una ventana de 30 minutos, Amazon EventBridge Scheduler los distribuye para evitar picos de carga en los sistemas destino. Si tu proceso necesita ejecutarse a una hora precisa, configura el modo en OFF.

Para mejorar la confiabilidad, Amazon EventBridge Scheduler incluye una política de reintentos configurable y soporte para colas de mensajes no procesados (Dead Letter Queues, DLQ). Cuando el scheduler no logra entregar el evento al destino, puede reintentar automáticamente con backoff exponencial. Si los reintentos se agotan, el scheduler envía el evento a la DLQ, una cola de Amazon SQS, para análisis posterior. Esta DLQ retiene las fallas de entrega. Los errores dentro del código de la función siguen otro camino, que explico más adelante.

El costo del schedu

ler es casi cero para la mayoría de los casos batch. Al momento de escribir este post, los primeros 14 millones de invocaciones por mes son gratuitos. Después, el costo es de $1.00 USD por millón de invocaciones. Un proceso que corre una vez por día representa 30 invocaciones mensuales, bien dentro del nivel gratuito. AWS Lambda, Amazon CloudWatch y Amazon Simple Notification Service (Amazon SNS) se cobran por separado. La sección de disponibilidad y costos detalla cada uno.

Arquitectura para chron usando event bridge scheduler

Arquitectura para Chron usando Event Bridge Scheduler


Crear tu primer schedule

Configurar el schedule desde la consola toma minutos. Navega a Amazon EventBridge, selecciona Scheduler en el menú lateral y elige Create schedule. Define el nombre, selecciona el tipo (cron, rate o one-time) y elige la zona horaria correcta para tu operación. En el mismo paso, decide si activas la ventana de tiempo flexible y define su duración.

En la sección de destino, selecciona Templated targets y elige AWS Lambda. Selecciona la función que contiene tu lógica de negocio y define el payload JSON que recibirá. Este payload puede incluir parámetros como el rango de fechas a procesar o el tipo de reporte a generar.

En la sección de permisos, elige si creas un rol nuevo para el schedule o si seleccionas un rol existente. Si seleccionas un rol existente, verifica que confíe en scheduler.amazonaws.com como principal.

Si tu equipo prefiere infraestructura como código (Infrastructure as Code, IaC), la siguiente plantilla de AWS CloudFormation define todos los recursos del ejemplo desde cero. No depende de recursos existentes. La plantilla recibe un parámetro, EmailNotificacion, con el correo que recibirá las alertas. Con ese parámetro, crea nueve recursos:

  • DLQEstadosCuenta (AWS::SQS::Queue): la DLQ del scheduler, que retiene las fallas de entrega.
  • RolEjecucionFuncion (AWS::IAM::Role): el rol de ejecución de la función.
  • GrupoRegistrosFuncion (AWS::Logs::LogGroup): el grupo de registros de la función, que conserva los registros durante 30 días.
  • FuncionEstadosCuenta (AWS::Lambda::Function): la función de AWS Lambda con el handler en Python.
  • RolScheduler (AWS::IAM::Role): el rol que asume el scheduler para invocar la función y escribir en la DLQ.
  • ProcesoEstadosDeCuenta (AWS::Scheduler::Schedule): el schedule.
  • TemaAlertasBatch (AWS::SNS::Topic): el tema de Amazon SNS que envía las alertas por correo.
  • AlarmaMensajesEnDLQ (AWS::CloudWatch::Alarm): la alarma que se activa cuando llega un evento a la DLQ.
  • AlarmaErroresScheduler (AWS::CloudWatch::Alarm): la alarma que se activa cuando el scheduler no logra invocar la función.
AWSTemplateFormatVersion: "2010-09-09"
Description: "Proceso batch de estados de cuenta con Amazon EventBridge Scheduler, AWS Lambda y alarmas de Amazon CloudWatch"

Parameters:
  EmailNotificacion:
    Type: String
    Description: "Correo que recibe las alertas de fallas, por ejemplo operaciones@example.com"

Resources:
  DLQEstadosCuenta:
    Type: AWS::SQS::Queue
    Properties:
      QueueName: "dlq-estados-cuenta"
      MessageRetentionPeriod: 1209600 # 14 días para diagnosticar fallas

  RolEjecucionFuncion:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal:
              Service: lambda.amazonaws.com
            Action: sts:AssumeRole
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole

  GrupoRegistrosFuncion:
    Type: AWS::Logs::LogGroup
    Properties:
      LogGroupName: "/aws/lambda/generar-estados-cuenta"
      RetentionInDays: 30 # Conserva los registros 30 días y luego los elimina

  FuncionEstadosCuenta:
    Type: AWS::Lambda::Function
    Properties:
      FunctionName: "generar-estados-cuenta"
      Runtime: python3.12
      Handler: index.handler
      Role: !GetAtt RolEjecucionFuncion.Arn
      Timeout: 900 # Máximo permitido por AWS Lambda: 15 minutos
      MemorySize: 512
      Code:
        ZipFile: |
          import json
          import logging

          logger = logging.getLogger()
          logger.setLevel(logging.INFO)

          def handler(event, context):
              logger.info("Evento recibido: %s", json.dumps(event))
              periodo = event.get("periodo", "sin-periodo")

              # Lógica de negocio: consultar cuentas, generar documentos, enviar correos.
              procesados = 0

              logger.info("Periodo %s terminado. Estados generados: %d", periodo, procesados)
              return {"status": "ok", "periodo": periodo, "procesados": procesados}

  RolScheduler:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal:
              Service: scheduler.amazonaws.com
            Action: sts:AssumeRole
      Policies:
        - PolicyName: InvocarLambdaYEnviarADLQ
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: Allow
                Action: lambda:InvokeFunction
                Resource: !GetAtt FuncionEstadosCuenta.Arn
              - Effect: Allow
                Action: sqs:SendMessage
                Resource: !GetAtt DLQEstadosCuenta.Arn

  ProcesoEstadosDeCuenta:
    Type: AWS::Scheduler::Schedule
    Properties:
      Name: "generacion-estados-cuenta-mensual"
      Description: "Genera estados de cuenta al cierre de cada mes"
      ScheduleExpression: "cron(0 2 L * ? *)"
      ScheduleExpressionTimezone: "America/Mexico_City"
      FlexibleTimeWindow:
        Mode: "FLEXIBLE"
        MaximumWindowInMinutes: 30
      Target:
        Arn: !GetAtt FuncionEstadosCuenta.Arn
        RoleArn: !GetAtt RolScheduler.Arn
        Input: '{"periodo": "cierre-mensual"}'
        RetryPolicy:
          MaximumRetryAttempts: 3
          MaximumEventAgeInSeconds: 3600
        DeadLetterConfig:
          Arn: !GetAtt DLQEstadosCuenta.Arn

  TemaAlertasBatch:
    Type: AWS::SNS::Topic
    Properties:
      TopicName: "alertas-procesos-batch"
      Subscription:
        - Protocol: email
          Endpoint: !Ref EmailNotificacion

  AlarmaMensajesEnDLQ:
    Type: AWS::CloudWatch::Alarm
    Properties:
      AlarmName: "dlq-estados-cuenta-con-mensajes"
      AlarmDescription: "Un evento del proceso de estados de cuenta agotó los reintentos y llegó a la DLQ"
      Namespace: AWS/SQS
      MetricName: ApproximateNumberOfMessagesVisible
      Dimensions:
        - Name: QueueName
          Value: !GetAtt DLQEstadosCuenta.QueueName
      Statistic: Maximum
      Period: 300
      EvaluationPeriods: 1
      Threshold: 1
      ComparisonOperator: GreaterThanOrEqualToThreshold
      TreatMissingData: notBreaching
      AlarmActions:
        - !Ref TemaAlertasBatch

  AlarmaErroresScheduler:
    Type: AWS::CloudWatch::Alarm
    Properties:
      AlarmName: "scheduler-errores-de-invocacion"
      AlarmDescription: "Amazon EventBridge Scheduler no logró invocar el destino"
      Namespace: AWS/Scheduler
      MetricName: TargetErrorCount
      Dimensions:
        - Name: ScheduleGroup
          Value: "default"
      Statistic: Sum
      Period: 300
      EvaluationPeriods: 1
      Threshold: 1
      ComparisonOperator: GreaterThanOrEqualToThreshold
      TreatMissingData: notBreaching
      AlarmActions:
        - !Ref TemaAlertasBatch

El recurso GrupoRegistrosFuncion crea el grupo de registros con el nombre que AWS Lambda usa por defecto para la función: /aws/lambda/generar-estados-cuenta. Si no defines este recurso, AWS Lambda crea el grupo automáticamente en la primera ejecución y conserva los registros sin fecha de expiración. Con RetentionInDays: 30, Amazon CloudWatch Logs elimina los registros después de 30 días. Ajusta ese valor según tus requisitos de auditoría.

Si ya tienes la función y la cola desplegadas, elimina los recursos DLQEstadosCuenta, RolEjecucionFuncion y FuncionEstadosCuenta de la plantilla. Después, reemplaza cada !GetAtt que apunta a esos recursos por el Amazon Resource Name (ARN) del recurso existente. En AlarmaMensajesEnDLQ, reemplaza !GetAtt DLQEstadosCuenta.QueueName por el nombre de tu cola. Si la función ya se ejecutó alguna vez, su grupo de registros ya existe y el despliegue falla al intentar crearlo de nuevo. En ese caso, elimina también GrupoRegistrosFuncion y configura la retención directamente sobre el grupo existente. Conserva RolScheduler y ProcesoEstadosDeCuenta, porque el scheduler necesita su propio rol en ambos casos. Conserva también el tema de Amazon SNS y las dos alarmas.

Este ejemplo usa un schedule de tipo cron que programa la ejecución para las 2 AM hora de Ciudad de México el último día de cada mes. Sobre ese schedule, la plantilla activa una ventana flexible de 30 minutos y reintenta hasta tres veces ante una falla. El campo Input define el payload que recibe la función.

El valor Timeout: 900 asigna a la función el máximo de 15 minutos que permite AWS Lambda. Si tu proceso se acerca a ese límite, cambia el destino por AWS Step Functions, AWS Batch o una tarea de Amazon ECS en AWS Fargate. El schedule, la política de reintentos, la DLQ y las alarmas se mantienen igual.

El handler de la función

La plantilla incluye el código en línea con ZipFile para mantener el ejemplo autocontenido. Este es el mismo handler mínimo en Python, fuera de la plantilla para leerlo con claridad:

import json
import logging

logger = logging.getLogger()
logger.setLevel(logging.INFO)


def handler(event, context):
    logger.info("Evento recibido: %s", json.dumps(event))
    periodo = event.get("periodo", "sin-periodo")

    # Lógica de negocio: consultar cuentas, generar documentos, enviar correos.
    procesados = 0

    logger.info("Periodo %s terminado. Estados generados: %d", periodo, procesados)
    return {"status": "ok", "periodo": periodo, "procesados": procesados}

El handler lee el payload que envía el scheduler y registra el inicio y el fin de la ejecución en Amazon CloudWatch Logs. Al terminar, devuelve un resumen. Reemplaza el comentario con tu lógica de negocio. Si ocurre un error, deja que la excepción se propague en lugar de capturarla en silencio. Así la ejecución queda marcada como fallida y aparece en las métricas de error.

Ten en cuenta un detalle que me costó entender: Amazon EventBridge Scheduler invoca AWS Lambda de forma asíncrona. Por eso, cada tipo de falla termina en un lugar distinto. La DLQ del scheduler retiene las fallas de entrega, como un permiso faltante o throttling. El destino en caso de falla (on-failure destination) de la función retiene los errores del código, después de que AWS Lambda agota sus propios reintentos asíncronos. Si solo configuras la DLQ del scheduler, los errores de tu código no llegan a ninguna cola. Para retenerlos, configura también el destino en caso de falla en la función.

Un handler idempotente

Esos reintentos tienen una consecuencia directa: la misma ejecución puede llegar más de una vez a la función. Cuando el código falla, AWS Lambda reintenta el evento asíncrono hasta dos veces por defecto. En casos poco frecuentes, también puede entregar el mismo evento más de una vez aunque no haya error. Si tu proceso envía estados de cuenta por correo, una ejecución duplicada significa clientes que reciben el mismo documento dos veces.

Te recomiendo diseñar el handler para que sea idempotente: ejecutarlo dos veces para el mismo periodo debe producir el mismo resultado que ejecutarlo una vez. Una forma práctica es registrar una marca por cada periodo procesado en una tabla de Amazon DynamoDB. Antes de procesar, el handler escribe la marca con una escritura condicional. Si la marca ya existe, otra ejecución ya procesó o está procesando ese periodo. En ese caso, el handler termina sin repetir el trabajo.

El payload del ejemplo envía siempre "periodo": "cierre-mensual", así que no sirve como clave: sería el mismo valor cada mes. Para obtener una clave única por ejecución, agrega al campo Input el atributo de contexto <aws.scheduler.scheduled-time>. El scheduler lo reemplaza por la fecha y hora programada de cada ejecución, en formato ISO 8601 y en UTC:

        Input: '{"periodo": "cierre-mensual", "fechaProgramada": "<aws.scheduler.scheduled-time>"}'

Después, agrega a la plantilla una tabla para las marcas. Con el modo de capacidad bajo demanda (PAY_PER_REQUEST), pagas solo por las escrituras que hace el handler, que para un proceso mensual son unas pocas al mes:

  TablaPeriodosProcesados:
    Type: AWS::DynamoDB::Table
    Properties:
      TableName: "periodos-procesados"
      BillingMode: PAY_PER_REQUEST
      AttributeDefinitions:
        - AttributeName: periodo
          AttributeType: S
      KeySchema:
        - AttributeName: periodo
          KeyType: HASH

Con la tabla creada, el handler reclama el periodo antes de procesarlo:

import json
import logging
import os

import boto3
from botocore.exceptions import ClientError

logger = logging.getLogger()
logger.setLevel(logging.INFO)

tabla = boto3.resource("dynamodb").Table(os.environ["TABLA_PERIODOS"])


def reclamar_periodo(clave):
    """Escribe la marca solo si el periodo no existe o si falló antes."""
    try:
        tabla.put_item(
            Item={"periodo": clave, "estado": "EN_PROCESO"},
            ConditionExpression="attribute_not_exists(periodo) OR estado = :fallido",
            ExpressionAttributeValues={":fallido": "FALLIDO"},
        )
        return True
    except ClientError as error:
        if error.response["Error"]["Code"] == "ConditionalCheckFailedException":
            return False
        raise


def actualizar_estado(clave, estado):
    tabla.update_item(
        Key={"periodo": clave},
        UpdateExpression="SET estado = :estado",
        ExpressionAttributeValues={":estado": estado},
    )


def handler(event, context):
    logger.info("Evento recibido: %s", json.dumps(event))
    clave = event["fechaProgramada"][:7]  # AAAA-MM de la fecha programada

    if not reclamar_periodo(clave):
        logger.info("Periodo %s ya procesado o en proceso. Se omite.", clave)
        return {"status": "omitido", "periodo": clave}

    try:
        # Lógica de negocio: consultar cuentas, generar documentos, enviar correos.
        procesados = 0
    except Exception:
        actualizar_estado(clave, "FALLIDO")
        raise

    actualizar_estado(clave, "COMPLETADO")
    logger.info("Periodo %s terminado. Estados generados: %d", clave, procesados)
    return {"status": "ok", "periodo": clave, "procesados": procesados}

La condición attribute_not_exists(periodo) OR estado = :fallido permite que un reintento procese de nuevo un periodo que falló. En cambio, bloquea un duplicado mientras otra ejecución trabaja o después de que terminó con éxito. Si ocurre una excepción, el handler marca el periodo como FALLIDO y la propaga. Así AWS Lambda registra la falla y aplica sus reintentos.

Para que este handler funcione, completa dos cambios en FuncionEstadosCuenta y su rol:

  • Agrega la variable de entorno TABLA_PERIODOS con el nombre de la tabla, usando Environment y !Ref TablaPeriodosProcesados.
  • Otorga a RolEjecucionFuncion los permisos dynamodb:PutItem y dynamodb:UpdateItem sobre el ARN de la tabla.

Este patrón tiene un límite que conviene conocer. Si la función excede el tiempo límite, el bloque except no se ejecuta y la marca queda en EN_PROCESO. Los reintentos posteriores omiten el periodo. Revisa esos casos cuando recibas una alerta y corrige la marca manualmente. También ten en cuenta que la marca protege el periodo completo. Si una falla ocurre a mitad del proceso, el reintento vuelve a empezar desde el principio. Para no reenviar los correos que ya salieron, aplica la misma técnica por cuenta: registra una marca por cada estado de cuenta enviado.

Si no puedes hacer el handler idempotente, desactiva los reintentos asíncronos de AWS Lambda. Esta opción tiene sentido cuando repetir el trabajo no es seguro, por ejemplo cuando el proceso llama a un sistema externo que no tolera duplicados. Agrega a la plantilla un recurso AWS::Lambda::EventInvokeConfig:

  ConfigInvocacionAsincrona:
    Type: AWS::Lambda::EventInvokeConfig
    Properties:
      FunctionName: !Ref FuncionEstadosCuenta
      Qualifier: "$LATEST"
      MaximumRetryAttempts: 0

Con MaximumRetryAttempts: 0, AWS Lambda no reintenta el evento cuando el código falla. La política de reintentos del scheduler sigue activa para las fallas de entrega. A cambio, una falla en el código ya no se recupera sola. Configura un destino en caso de falla con una alarma sobre él para enterarte y reprocesar el periodo manualmente. Esta opción reduce los duplicados, pero no los elimina: las entregas duplicadas poco frecuentes todavía pueden ocurrir. Solo un handler idempotente te protege en todos los casos.

Permisos del scheduler

Al configurar los permisos, el aspecto menos obvio es que Amazon EventBridge Scheduler necesita su propio rol de AWS Identity and Access Management (IAM) para invocar el destino. Ese rol debe confiar en scheduler.amazonaws.com como principal. Sin esa confianza, las invocaciones fallan y solo detectas la falla si monitoreas las métricas de Amazon CloudWatch del scheduler. La alarma sobre TargetErrorCount de la siguiente sección cubre justo ese caso.

Junto con la relación de confianza, debes otorgar al rol ambos permisos que define la plantilla:

  • lambda:InvokeFunction sobre la función FuncionEstadosCuenta, para que el scheduler ejecute tu lógica de negocio.
  • sqs:SendMessage sobre la cola DLQEstadosCuenta, para que el scheduler envíe las fallas de entrega a la DLQ.

Si falta el primer permiso, el proceso nunca se ejecuta. Si falta el segundo, el scheduler no puede enviar las fallas de entrega a la cola. En ese caso, pierdes el rastro de la falla justo cuando más lo necesitas.

Alarmas para enterarte antes que tus usuarios

La DLQ del scheduler retiene las fallas de entrega, pero una cola no avisa a nadie por sí sola. Si nadie la revisa, vuelves al punto de partida: te enteras de la falla cuando los usuarios se quejan. Por eso la plantilla agrega dos alarmas de Amazon CloudWatch que publican en el tema TemaAlertasBatch de Amazon SNS.

  • AlarmaMensajesEnDLQ vigila la métrica ApproximateNumberOfMessagesVisible de la DLQ en el namespace AWS/SQS. Se activa cuando la cola contiene al menos un mensaje. Esa condición indica que el scheduler agotó los tres reintentos de entrega y el proceso no se ejecutó.
  • AlarmaErroresScheduler vigila la métrica TargetErrorCount del namespace AWS/Scheduler para el grupo de schedules default. Se activa con el primer error de invocación, aunque un reintento posterior tenga éxito. Funciona como aviso temprano de un permiso faltante o de throttling.

Puedes usar una sola alarma o ambas. La alarma de la DLQ te avisa de una falla definitiva. La alarma de TargetErrorCount te avisa antes, pero también puede notificar errores que el scheduler resolvió solo con un reintento. En ambas, TreatMissingData: notBreaching evita alertas falsas en los días en que el proceso no corre.

Al desplegar la plantilla, Amazon SNS envía un correo de confirmación a la dirección de EmailNotificacion. Hasta que confirmes la suscripción, las alertas no llegan. Revisa ese correo antes del primer cierre de mes.

Un detalle sobre la alarma de la DLQ: Amazon CloudWatch notifica solo cuando la alarma cambia de estado. Mientras el mensaje siga en la cola, la alarma permanece en estado ALARM y no vuelve a notificar ante una nueva falla. Después de diagnosticar el problema, reprocesa o elimina los mensajes de la DLQ. Así la alarma regresa a OK y queda lista para la próxima falla.

Ninguna de las dos alarmas detecta errores dentro del código de la función. Esos errores no llegan a la DLQ del scheduler: después de sus reintentos asíncronos, AWS Lambda los envía al destino en caso de falla de la función. Apunta ese destino a otra cola y crea una alarma equivalente sobre ella.


Disponibilidad y costos

Amazon EventBridge Scheduler está disponible en la mayoría de las regiones de AWS, incluidas regiones de América Latina. Su nivel gratuito cubre 14 millones de invocaciones por mes. Por eso, el disparador de casi cualquier operación batch estándar no genera costo adicional.

AWS Lambda tiene su propio nivel gratuito, independiente del nivel gratuito de Amazon EventBridge Scheduler. Al momento de escribir este post, incluye 1 millón de solicitudes y 400,000 GB-segundo de cómputo por mes. Un GB-segundo combina la memoria asignada a la función con su tiempo de ejecución. La función del ejemplo usa 512 MB, es decir, 0.5 GB. Si cada corrida tarda 10 minutos, consume 300 GB-segundo (0.5 GB × 600 segundos). Aun con una corrida diaria de esa duración, el consumo llega a 9,000 GB-segundo al mes, dentro del nivel gratuito.

Amazon CloudWatch Logs puede generar costos menores cuando ingiere y almacena registros. Al momento de escribir este post, su nivel gratuito incluye 5 GB de datos de registro por mes. Un handler que registra solo el inicio y el fin de cada ejecución genera pocos kilobytes por corrida. Si tu lógica de negocio registra cada cuenta procesada, el volumen crece rápido. Por defecto, el grupo de registros de una función conserva los registros sin fecha de expiración. Por eso la plantilla define GrupoRegistrosFuncion con una retención de 30 días, que limita el costo de almacenamiento.

Las alarmas y las notificaciones también caben en los niveles gratuitos. Al momento de escribir este post, Amazon CloudWatch incluye 10 alarmas estándar por mes sin costo. Después, cada alarma estándar cuesta $0.10 USD al mes. Amazon SNS incluye 1,000 notificaciones por correo electrónico por mes sin costo. Las dos alarmas del ejemplo y unas pocas alertas al mes quedan dentro de ambos límites.

Compara con el modelo anterior. Una instancia Amazon EC2 t3.small bajo demanda (On-Demand) que corre 24/7 para ejecutar demonios cuesta aproximadamente $15 USD al mes. Este precio aproximado corresponde a la región Este de EE. UU. (Norte de Virginia) us-east-1 y puede cambiar. Ese monto no incluye el tiempo que el equipo de IT dedica a mantenerla. Amazon EventBridge Scheduler, AWS Lambda, Amazon CloudWatch y Amazon SNS cuestan centavos al mes para el mismo caso de uso. Si tu consumo se mantiene dentro de los niveles gratuitos, no cuestan nada.

Nota sobre precios: Los precios de este post son aproximados, corresponden a la fecha de publicación y están sujetos a cambios. El precio de Amazon EC2 corresponde a la modalidad bajo demanda (On-Demand) y varía según la región y el sistema operativo. Para estimar el costo de tu propio escenario, usa la Calculadora de precios de AWS. Para consultar las tarifas vigentes, visita la página de precios de Amazon EventBridge y la página de precios de AWS Lambda. También puedes revisar la página de precios de Amazon CloudWatch y la página de precios de Amazon SNS.


El cambio que importa

Este cambio es principalmente operativo: la visibilidad de las fallas pasa de los usuarios al sistema. El equipo deja de administrar infraestructura que no agrega valor y empieza a recibir visibilidad real sobre sus procesos. Amazon CloudWatch Logs captura los registros de cada ejecución de AWS Lambda y los conserva durante el periodo de retención que definiste. Las métricas de Amazon CloudWatch muestran tasas de éxito y falla. La DLQ del scheduler retiene las fallas de entrega, y el destino en caso de falla de la función retiene los errores del código.

Con las alarmas de Amazon CloudWatch y las notificaciones de Amazon SNS, el equipo detecta las fallas de entrega en minutos. Así deja de depender de los reportes de los usuarios. El correo de alerta llega minutos después de que el scheduler registra la falla. Para detectar los errores del código, configura una alarma adicional sobre la función de AWS Lambda.

Migrar un proceso batch nocturno a Amazon EventBridge Scheduler más AWS Lambda toma horas, no semanas. El resultado es cero servidores que administrar, un costo casi nulo y alta disponibilidad para tus procesos batch.

Sobre los autores

 Rodrigo Cabrera es un Sr. Solutions Architect basado en México con más de 20 años experiencia en TI, y más de 8 años implementando soluciones sin servidores, experto en arquitecturas basadas en eventos, arquitecturas modernas y sistemas distribuidos.

Sobre los revisores

Ana Barragán es Solutions Architect en Amazon Web Services (AWS) Colombia, con más de 12 años de experiencia en la industria de tecnología y cerca de 5 años en AWS, donde trabaja principalmente con clientes del sector Financial Services (FSI), especialmente en medios de pago. Antes de AWS se desempeñó como desarrolladora de software, líder técnica y arquitecta de aplicaciones e integraciones. Es AWS Golden Jacket y miembro de la Technical Field Community de Serverless, con enfoque en APIs. Además, promueve activamente la diversidad, inclusión y participación de más mujeres en tecnología.