Implementing Serverless Scheduled Tasks (CloudWatch Events / Cron)
Scheduled Tasks in serverless replaces traditional cron jobs without constantly running servers. EventBridge Scheduler (AWS) or Cloud Scheduler (GCP) trigger Lambda on schedule. Advantages: no server maintenance, built-in retry, centralized schedule management.
AWS EventBridge Scheduler vs CloudWatch Events
CloudWatch Events (old): works, but limited. No concurrency management, no default retry.
EventBridge Scheduler (2022, recommended): for new projects:
- Flexible time window (run any moment within N minutes)
- Retry policy with backoff
- DLQ for failed invocations
- Timezone support for schedule
- One-time schedules (at-invokes)
resource "aws_scheduler_schedule" "daily_report" {
name = "daily-sales-report"
flexible_time_window {
mode = "FLEXIBLE"
maximum_window_in_minutes = 15
}
schedule_expression = "cron(0 8 * * ? *)"
schedule_expression_timezone = "Europe/Moscow"
target {
arn = aws_lambda_function.generate_report.arn
role_arn = aws_iam_role.scheduler_role.arn
input = jsonencode({
report_type = "daily_sales"
format = "pdf"
})
retry_policy {
maximum_event_age_in_seconds = 3600
maximum_retry_attempts = 3
}
dead_letter_config {
arn = aws_sqs_queue.scheduler_dlq.arn
}
}
}
AWS Cron Syntax
AWS uses 6-field cron (year added):
cron(minutes hours day-of-month month day-of-week year)
cron(0 12 * * ? *) # Every day at 12:00 UTC
cron(0 */6 * * ? *) # Every 6 hours
cron(0 9 ? * MON-FRI *) # Every weekday at 9:00
cron(0 0 1 * ? *) # First day of month at 00:00
cron(0 8 ? * MON *) # Every Monday at 8:00
rate(5 minutes) # Every 5 minutes
rate(1 hour) # Every hour
Lambda Handler for Scheduled Task
import json
import logging
from datetime import datetime
logger = logging.getLogger()
logger.setLevel(logging.INFO)
def handler(event, context):
task_type = event.get('task_type', 'unknown')
started_at = datetime.utcnow().isoformat()
logger.info(f"Starting scheduled task: {task_type} at {started_at}")
try:
if task_type == 'cleanup_expired_sessions':
result = cleanup_expired_sessions()
elif task_type == 'send_weekly_digest':
result = send_weekly_digest()
elif task_type == 'sync_inventory':
result = sync_inventory_from_erp()
else:
raise ValueError(f"Unknown task type: {task_type}")
logger.info(f"Task completed: {json.dumps(result)}")
return {'status': 'success', 'task': task_type, **result}
except Exception as e:
logger.error(f"Task failed: {e}", exc_info=True)
raise
Idempotency for Scheduled Tasks
If task runs twice (flexible window or retry) — result must be the same:
import boto3
import hashlib
from datetime import datetime
dynamodb = boto3.resource('dynamodb')
task_locks = dynamodb.Table('task_locks')
def run_with_idempotency(task_fn, task_id: str, time_window_minutes: int = 60):
"""Guarantee task executes only once per window"""
window_key = f"{task_id}:{datetime.utcnow().strftime('%Y%m%d%H')}"
try:
task_locks.put_item(
Item={
'task_key': window_key,
'ttl': int(time.time()) + time_window_minutes * 60
},
ConditionExpression='attribute_not_exists(task_key)'
)
except task_locks.meta.client.exceptions.ConditionalCheckFailedException:
print(f"Task {task_id} already ran in this window, skipping")
return None
return task_fn()
Monitoring Scheduled Tasks
Heartbeat pattern — each successful task pings monitoring. If ping missing — task didn't run:
import requests
def send_healthcheck_ping(check_id: str):
"""Ping Healthchecks.io or UptimeRobot"""
requests.get(
f"https://hc-ping.com/{check_id}",
timeout=5
)
def handler(event, context):
result = perform_task()
# Notify monitoring of success
send_healthcheck_ping(os.environ['HEALTHCHECK_ID'])
return result
Healthchecks.io — specialized service for cron monitoring. If ping missing at expected time — notification.
Multiple Environments
Scheduled tasks often unneeded in staging/dev:
resource "aws_scheduler_schedule" "daily_report" {
count = var.environment == "production" ? 1 : 0
name = "daily-sales-report"
...
}
Or use state = "DISABLED" for non-production.
Typical Scheduled Tasks
- Cleanup expired sessions / temp files
- Generate reports and digests
- Sync data with external APIs (hourly)
- Check and update SSL certificates
- Data backups
- Cache updates (sitemaps, categories, counters)
- Process deferred tasks (send scheduled emails)
Implementation Timeline
- EventBridge Scheduler + basic Lambda — 1-2 days
- Idempotency + retry policy — 1 day
- Monitoring (Healthchecks.io + alerts) — 0.5-1 day
- Multiple tasks + testing — 1-2 days







