AWS EventBridge Cron Timezone - Fire on Specific Local Time - amazon-web-services

We have set an EventBridge to trigger a Lambda. It should run every day at 9:30AM Local time (US EST/EDT in my case). The problem is the date seems to be set in EventBridge by UTC. Is there a way to get this to always be based on a specific timezone? IE 9:30AM regardless of the season?

Updated: November 2022
Use EventBridge Scheduler as it allows you to schedule events at specific date-time along with timezone. Also supports one-time schedules.
Introducing Amazon EventBridge Scheduler - AWS Blog
Original: July 2021
Either schedule another event to adjust the first event, or execute the lambda at both 9:30 EST and 9:30 EDT and have the lambda figure out which one should run.
Run another lambda at 2am and 3am local time that can adjust the schedule of the first lambda for daylight saving time. You can use the lambda language's implementation to decide whether the event needs to be adjusted.
You could also schedule your original lambda to run at both the daylight-saving-adjusted and the non-adjusted time (so 14:30 UTC for EDT and 13:30 UTC for EST). Then the lambda could decide whether it was the proper time to execute based on a calendar check.
I prefer the first option because it's a clearer separation of duties.

Sadly, you can't do this, as only UTC time zone is used:
All scheduled events use UTC time zone and the minimum precision for schedules is 1 minute.
You would need custom solution for what you want to do. For example, let AWS EventBridge trigger a lambda function and the function evaluates what else should be triggered based on its conversions of UTC to local times.

As can be read in another answer here, this is possible using the EventBridge Scheduler. In below example I'm using CDK to schedule a lambda at 6 am and 8 am, from Monday to Friday.
// Lambda
const func = new NodejsFunction(this, 'func-id', {
runtime: Runtime.NODEJS_18_X
});
// Role
const role = new Role(this, 'role-id', {
managedPolicies: [{ managedPolicyArn: 'arn:aws:iam::aws:policy/service-role/AWSLambdaRole' }],
assumedBy: new ServicePrincipal('scheduler.amazonaws.com')
});
// EventBridge Schedule
new CfnSchedule(this, 'schedule-id', {
flexibleTimeWindow: {
mode: 'OFF',
},
scheduleExpression: 'cron(0 6,8 ? * MON-FRI *)',
scheduleExpressionTimezone: 'Europe/Amsterdam',
target: {
arn: func.functionArn,
roleArn: role.roleArn,
},
});

Related

How to prevent serverless cron from being changed by Daylight Savings time? [duplicate]

We have set an EventBridge to trigger a Lambda. It should run every day at 9:30AM Local time (US EST/EDT in my case). The problem is the date seems to be set in EventBridge by UTC. Is there a way to get this to always be based on a specific timezone? IE 9:30AM regardless of the season?
Updated: November 2022
Use EventBridge Scheduler as it allows you to schedule events at specific date-time along with timezone. Also supports one-time schedules.
Introducing Amazon EventBridge Scheduler - AWS Blog
Original: July 2021
Either schedule another event to adjust the first event, or execute the lambda at both 9:30 EST and 9:30 EDT and have the lambda figure out which one should run.
Run another lambda at 2am and 3am local time that can adjust the schedule of the first lambda for daylight saving time. You can use the lambda language's implementation to decide whether the event needs to be adjusted.
You could also schedule your original lambda to run at both the daylight-saving-adjusted and the non-adjusted time (so 14:30 UTC for EDT and 13:30 UTC for EST). Then the lambda could decide whether it was the proper time to execute based on a calendar check.
I prefer the first option because it's a clearer separation of duties.
Sadly, you can't do this, as only UTC time zone is used:
All scheduled events use UTC time zone and the minimum precision for schedules is 1 minute.
You would need custom solution for what you want to do. For example, let AWS EventBridge trigger a lambda function and the function evaluates what else should be triggered based on its conversions of UTC to local times.
As can be read in another answer here, this is possible using the EventBridge Scheduler. In below example I'm using CDK to schedule a lambda at 6 am and 8 am, from Monday to Friday.
// Lambda
const func = new NodejsFunction(this, 'func-id', {
runtime: Runtime.NODEJS_18_X
});
// Role
const role = new Role(this, 'role-id', {
managedPolicies: [{ managedPolicyArn: 'arn:aws:iam::aws:policy/service-role/AWSLambdaRole' }],
assumedBy: new ServicePrincipal('scheduler.amazonaws.com')
});
// EventBridge Schedule
new CfnSchedule(this, 'schedule-id', {
flexibleTimeWindow: {
mode: 'OFF',
},
scheduleExpression: 'cron(0 6,8 ? * MON-FRI *)',
scheduleExpressionTimezone: 'Europe/Amsterdam',
target: {
arn: func.functionArn,
roleArn: role.roleArn,
},
});

Schedule AWS lambda in a interval with immediately trigger

To run lambda in an interval, I could use EventBridge rule: https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-create-rule-schedule.html
For example, if I set the rule to 7 days, the lambda will execute at 7 days after the lambda is created.
What if I need to run this lambda immediately after its creation and also run this lambda in an interval?
How can I do this programmatically or in CDK?
Note: This solution only works for those who use AWS CodeBuild to deploy their Lambdas.
In this sample project (https://github.com/dashmug/us-covid-stats) I did a while back, I configured the Lambda to also have another trigger based on CodeBuild's "Build Succeeded" event.
In https://github.com/dashmug/us-covid-stats/blob/main/backend/serverless.yml#L71,
RefreshDataFromSources:
handler: us_covid_stats/etl/handler.refresh_data_from_sources
events:
- schedule:
enabled: false
rate: rate(1 day)
- cloudwatchEvent:
enabled: false
event:
source:
- aws.codebuild
detail-type:
- CodeBuild Build State Change
detail:
build-status:
- SUCCEEDED
project-name:
- us-covid-stats-deployment-backend
you'll see that the Lambda is normally triggered once daily. But also on top of the daily schedule, it is triggered when the deployment succeeds.
You can create a Cloudwatch Event Rule that occurs a single time using a very specific schedule expression thanks to the fact that Cloudwatch Events supports the Year for cron expressions. You would need to know, roughly, how long until deployment is complete. While this method wouldn't be instant it could be close enough to serve your purpose without complicated custom resources or post deployment triggering as long as a buffer of some minutes is okay.
For example in Typescript this might look like follows:
Get the future date you'd like to target and add some minutes (10 in this case):
const date = new Date();
const minutesToAdd = 10;
const future = new Date(date.getTime() + minutesToAdd * 60000);
Convert that future date to a cron expression for exactly the that single date then store the expression with the Day of Week set to ? for "no specific value" since we are setting the day of the month.
const minutes = future.getUTCMinutes();
const hours = future.getUTCHours();
const days = future.getUTCDay();
const months = future.getUTCMonth() + 1;
const years = future.getUTCFullYear();
let futureCron = `${minutes} ${hours} ${days} ${months} ? ${years}`;
Create the schedule expression
const futureEvent = events.schedule.expression('cron(' + dateToCron(future) + ')');
Use the expression to schedule an event rule
new events.Rule(this, 'immediateTrigger', {
schedule: futureEvent
targets: [new targets.LambdaFunction(someHandler)]
}
This would result in a scheduled event that occurs only at a single point in UTC time. For example if it was 2:55PM on Dec 5th 2021 when you deployed it would create an expression for 03:05 PM, on day 5 of the month, only in December, only in 2021
I had a need for this exact type of setup so I created a library for this purpose that simplifies the above process to generate schedule expressions for both one time OnDeploy or one time At a given future date. While anyone is free to use this you should understand why it works and if it's the right choice for your needs.

Invoking a Lambda function every 5 seconds

My problem
I'd like to invoke an AWS Lambda function every 5 seconds, for some monitoring purposes.
According to the docs,
Rate frequencies of less than one minute are not supported.
What have I tried
STFW.
My Question
Can I autonomously invoke an AWS Lambda function every 5 seconds?
Although I can not recommend this way, but if you really need to execute a Lambda function every 5 seconds, you can try this:
Create a Lambda function A which is executed every minute.
Create a Lambda function B which is triggered every 5 seconds by Lambda function A. (A triggers B, waits 5 seconds, triggers B, waits 5 seconds, ...)
Stop Lambda function A after approximately one minute. (You can read the remaining miliseconds from the context object => if you reach >55 seconds, stop execution)
Please carefully consider if you really need this.
you can use step functions and lambda for that - look at https://aws.amazon.com/blogs/architecture/a-serverless-solution-for-invoking-aws-lambda-at-a-sub-minute-frequency/
Thought I'd still answer this.
Your whole problem is that serverless is not a great fit for near constant workloads. They are more designed for quickly scaling in and out. But if that isn't your use-case then you will almost certainly be better off cost wise using an ec2 instance, you can even reserve one. But I do get that you lose the high-availability and some of the other benefits of Lambda, which might want you to still go with this approach.
If you want to use a sledge hammer to make this approach work you could use a StepFunction. StepFunctions can wait in terms of seconds, but the only catch is that they are not suppose to run indefinitely and will die after 1 year, so your StepFunction will need to start another StepFunction before it dies, or you will have to by some other mechanism, i.e. cloud watch alarms.
This approach also costs a bit of money for the number of invocations you are talking about, would ring up a cost of over
(31,540,000 [seconds/year] / 5 [seconds/invocation] x $0.000025 = $157.7)
Plus your Lambda Costs, (and I think you actually need 2 state transitions so I think you double that figure)
Might be fine if you are only going to run 1. But running lets say 100 would be well over 10k! Most certainly an ec2 instance will cost you less.
Here's the full take in serverless with a step function executing every second - forever and a day:
service:
name: your-service
plugins:
- serverless-python-requirements
- serverless-step-functions
- serverless-pseudo-parameters
custom:
pythonRequirements:
dockerizePip: non-linux
region: eu-west-1
stage: dev
package:
exclude:
- node_modues/**
- venv/**
provider:
name: aws
iamManagedPolicies:
- arn:aws:iam::aws:policy/AWSStepFunctionsFullAccess
- arn:aws:iam::aws:policy/AWSLambdaFullAccess
runtime: python3.7
versionFunctions: false
region: ${self:custom.region}
stage: ${self:custom.stage}
environment:
PUSH_FUNCTION: arn:aws:lambda:#{AWS::Region}:#{AWS::AccountId}:function:${self:service}-${self:custom.stage}-push
functions:
schedulerStart:
handler: scheduler.start
events:
- schedule: rate(1 minute)
environment:
STATEMACHINE_ARN: ${self:resources.Outputs.SchedulerStateMachine.Value}
scheduler:
handler: scheduler.handle
push:
handler: scheduler.push
stepFunctions:
stateMachines:
everySecond:
name: SchedulerStateMachine
definition:
Comment: "Step Function invoked every minute"
StartAt: ConfigureCount
States:
ConfigureCount:
Type: Pass
Result:
index: 0
count: 60
ResultPath: "$.iterator"
Next: "Iterator"
Iterator:
Type: Task
Resource: "arn:aws:lambda:#{AWS::Region}:#{AWS::AccountId}:function:${self:service}-${self:custom.stage}-scheduler"
ResultPath: "$.iterator"
Next: "IsCountReached"
IsCountReached:
Type: Choice
Choices:
- Variable: "$.iterator.continue"
BooleanEquals: true
Next: "Wait"
Default: "Done"
Wait:
Type: Wait
Seconds: 1
Next: "Iterator"
Done:
Type: Pass
End: true
resources:
Outputs:
SchedulerStateMachine:
Description: The ARN of the state machine
Value:
Ref: SchedulerStateMachine
scheduler.py
import os
import boto3
lambda_client = boto3.client('lambda')
step_functions_client = boto3.client('stepfunctions')
def start(event, context):
step_functions_client.start_execution(stateMachineArn=os.environ['STATEMACHINE_ARN'])
return {}
def handle(event, context):
index = event['iterator']['index'] + 1
lambda_client.invoke(
FunctionName=os.environ['PUSH_FUNCTION'],
InvocationType='Event'
)
return {
'index': index,
'continue': index < event['iterator']['count'],
'count': event['iterator']['count']
}
def push(event, context):
print("Executed every seconds!")
return{}
Assuming that a retry or a slight time drift isn't the end of the world. Another possible method would be to cron an orchestration lambda once a minute that then turns around and creates delayed sqs messages. You can then use the sqs lambda trigger to invoke your target lambda.
Use the SQS SendMessage DelaySeconds API (it's native likely all of AWS sdk's) as an off set for the number of sub minute calls one wants to make. For example if you want to make a call every 5 seconds you would create 12 messages
delaySeconds: 00
delaySeconds: 05
delaySeconds: 10
delaySeconds: 15
...
https://docs.aws.amazon.com/AWSSimpleQueueService/latest/APIReference/API_SendMessage.html
With this approach one must pay special attention to sqs's lambda invoke and retry logic. If multiple tries is a problem be sure to setup a DLQ and consider using a FIFO queue. AWS, by design, may invoke the target lambda more then once. Note that standard SQS queues are: at-least-once delivery, and FIFO queues are: exactly-once processing. It's also possible to have some time drift with this approach as CloudWatch may invoke the orchestration lambda a few seconds "after" the minute.
https://aws.amazon.com/sqs/faqs/
Is this approach better then long running the orchestration lambda, or even a step function? Eh, that depends on a lot of things. Personally I find the idea of leaving a lambda running in a wait state wasteful, you have to pay for that lambda doing nothing most of the time, and if it's a large lambda those costs are not trivial. Using a step function seems like overkill and a much more complex setup then a sqs integration.
At least to me, it seems better to have it spin up, schedule a set of tasks via sqs and die. This is more complex then a long running lambda, has some concurrency issues one must contend with, but it is all "serverless" if that's where one wants to be.
With all that said, lambda isn't the best fit for everything- if this is a "forever" kind of task consider using ECS/Fargate/EC2, as there is a good chance it's cheaper and ultimately less complex.
There is now a workaround using CloudWatch Events Rule which would trigger a Step Function every minute. The step function would then trigger a given amount of times an "iterator" Lambda, which will invoke your lambda. Here is the AWS blog post.
Cron only allows for a minimum of one minute. What you could do is write a shell script with an infinite loop that runs your task, and then sleeps for 5 seconds. That way your task would be run more or less every 5 seconds, depending on how long the task itself takes.
But it really does sound like you're doing something that you probably shouldn't be doing though. This feels wrong.
Whatever time you invoke your lambda, lets say 1 min. Divide it with 5 secs which u can keep as configurable as lambda gets invoked.
Then let the loop wait and repeat for every 5 secs inside your handler.
This way you will not need any new lambda and lambda call charges.

Can I schedule a lambda function execution with a lambda function?

I'm looking for the ability to programmatically schedule a lambda function to run a single time with another lambda function. For example, I made a request to myFirstFunction with date and time parameters, and then at that date and time, have mySecondFunction execute. Is that possible only with stateless AWS services? I'm trying to avoid an always-on ec2 instance.
Most of the results I'm finding for scheduling a lambda functions have to do with cloudwatch and regularly scheduled events, not ad-hoc events.
This is a perfect use case for aws step functions.
Use Wait state with SecondsPath or TimestampPath to add the required delay before executing the Next State.
What you're tring to do (schedule Lambda from Lambda) it's not possible with the current AWS services.
So, in order to avoid an always-on ec2 instance, there are other options:
1) Use AWS default or custom metrics. You can use, for example, ApproximateNumberOfMessagesVisible or CPUUtilization (if your app fires a big CPU utilization when process a request). You can also create a custom metric and fire it when your instance is idle (depending on the app that's running in your instance).
The problem with this option is that you'll waste already paid minutes (AWS always charge a full-hour, no matter if you used your instance for 15 minutes).
2) A better option, in my opinion, would be to run a Lambda function once per minute to check if your instances are idle and shut them down only if they are close to the full hour.
import boto3
from datetime import datetime
def lambda_handler(event, context):
print('ManageInstances function executed.')
environments = [['instance-id-1', 'SQS-queue-url-1'], ['instance-id-2', 'SQS-queue-url-2'], ...]
ec2_client = boto3.client('ec2')
for environment in environments:
instance_id = environment[0]
queue_url = environment[1]
print 'Instance:', instance_id
print 'Queue:', queue_url
rsp = ec2_client.describe_instances(InstanceIds=[instance_id])
if rsp:
status = rsp['Reservations'][0]['Instances'][0]
if status['State']['Name'] == 'running':
current_time = datetime.now()
diff = current_time - status['LaunchTime'].replace(tzinfo=None)
total_minutes = divmod(diff.total_seconds(), 60)[0]
minutes_to_complete_hour = 60 - divmod(total_minutes, 60)[1]
print 'Started time:', status['LaunchTime']
print 'Current time:', str(current_time)
print 'Minutes passed:', total_minutes
print 'Minutes to reach a full hour:', minutes_to_complete_hour
if(minutes_to_complete_hour <= 2):
sqs_client = boto3.client('sqs')
response = sqs_client.get_queue_attributes(QueueUrl=queue_url, AttributeNames=['All'])
messages_in_flight = int(response['Attributes']['ApproximateNumberOfMessagesNotVisible'])
messages_available = int(response['Attributes']['ApproximateNumberOfMessages'])
print 'Messages in flight:', messages_in_flight
print 'Messages available:', messages_available
if(messages_in_flight + messages_available == 0):
ec2_resource = boto3.resource('ec2')
instance = ec2_resource.Instance(instance_id)
instance.stop()
print('Stopping instance.')
else:
print('Status was not running. Nothing is done.')
else:
print('Problem while describing instance.')
UPDATE - I wouldn't recommend using this approach. Things changed in when TTL deletions happen and they are not close to TTL time. The only guarantee is that the item will be deleted after the TTL. Thanks #Mentor for highlighting this.
2 months ago AWS announced DynamoDB item TTL, which allows you to insert an item and mark when you wish for it to be deleted. It will be deleted automatically when the time comes.
You can use this feature in conjunction with DynamoDB Streams to achieve your goal - your first function inserts an item to a DynamoDB table. The record TTL should be when you want the second lambda triggered. Setup a stream that triggers your second lambda. In this lambda you will identify deletion events and if that's a delete then run your logic.
Bonus point - you can use the table item as a mechanism for the first lambda to pass parameters to the second lambda.
About DynamoDB TTL:
https://aws.amazon.com/blogs/aws/new-manage-dynamodb-items-using-time-to-live-ttl/
It does depend on your use case, but the idea that you want to trigger something at a later date is a common pattern. The way I do it serverless is I have a react application that triggers an action to store a date in the future. I take the date format like 24-12-2020 and then convert it using date(), having researched that the date format mentioned is correct, so I might try 12-24-2020 and see what I get(!). When I am happy I convert it to a Unix number in javascript React I use this code:
new Date(action.data).getTime() / 1000
where action.data is the date and maybe the time for the action.
I run React in Amplify (serverless), I store that to dynamodb (serverless). I then run a Lambda function (serverless) to check my dynamodb for any dates (I actually use the Unix time for now) and compare the two Unix dates now and then (stored) which are both numbers, so the comparison is easy. This seems to me to be super easy and very reliable.
I just set the crontab on the Lambda to whatever is needed depending on the approximate frequency required, in most cases running a lambda every five minutes is pretty good, although if I was only operating this in a certain time zone for a business weekday application I would control the Lambda a little more. Lambda is free for the first 1m functions per month and running it every few minutes will cost nothing. Obviously things change, so you will need to look that up in your area.
You will never get perfect timing in this scenario. It will, however, for the vast majority of use cases be close enough according to the timing settings of the Lambda function, you could set it up to check every minute or just once per day, it all depends on your application.
Alternatively, If I wanted an instant reaction to an event I might use SMS, SQS, or Kinesis to instantly stream a message, it all depends on your use case.
I'd opt for enqueuing deferred work to SQS using message timers in myFirstFunction.
Currently, you can't use SQS as a Lambda event source, but you can either periodically schedule mySecondFunction to check the queue via scheduled CloudWatch Events (somewhat of a variant of the other options you've found) or use a CloudWatch alarm on the ApproximateNumberOfMessagesVisible to fire an SNS message to a Lambda and avoid constant polling for queues that are frequently inactive for long periods.

Scheduled AWS Lambda Task at less than 1 minute frequency

We are trying to develop a true lambda-based application in which certain tasks need to be performed at schedules of variable frequencies. They are actually polling for data, and at certain times of the day, this polling can be as slow as once every hour, while at other times, it has to be once every second. I have looked at the options for scheduling (e.g. Using AWS Lambda with Scheduled Events and AWS re:Invent 2015 | (CMP407) Lambda as Cron: Scheduling Invocations in AWS Lambda), but it seems that short of spinning up an EC2 instance or a long-running lambda, there's no built-in way of firing up lambdas at a frequency of less than one minute. The lambda rate expression doesn't have a place for seconds. Is there a way to do this without an EC2 instance or long-running lambda? Ideally, something that can be done without incurring additional cost for scheduling.
You theoretically can wire up a high-frequency task-trigger without an EC2 instance or long-running lambda using an AWS Step Function executed by a CloudWatch Events scheduled event (see Emanuele Menga's blog post for an example), but that approach would actually be more expensive than the other two options, according to my current (as of May 2019) estimates:
(assuming 1 year = 31536000 seconds)
Step Function:
$0.0250 per 1000 state transitions
2 state transitions per tick (Wait, Task) + at least 1 per minute (for setup/configuration)
31536000 * (2 + 1/60) * 0.0250 / 1000 = $1589.94/year, or as low as $65.70/year for lower frequency trigger (2 ticks per minute)
Lambda Function:
$0.000000208 per 100ms (for smallest 128mb function)
31536000 * 0.000000208 * 10 = $65.595488/year
EC2 Instance:
t3a.nano is $0.0047 per hour on-demand, or as low as $0.0014 using Spot instances
31536000 * 0.0047 / 3600 = $41.172/year, or $12.264/year using Spot instances
So there will be some scheduling cost, but as you can see the cost is not that much.
Currently lambda functions are invoked, at the very least, every 1 minute from Cloudwatch schedule events.
A solution that might work would be the following:
Setup an EC2 instance and from a program, that you will run as a background job, use the aws sdk to invoke your lambda function. Example:
while True:
invoke lambda function via aws sdk
sleep for x seconds
At this point in time AWS Lambda allows functions to be scheduled to run every 5 minutes with a maximum execution time of 5 minutes.
This means if you want to run an AWS Lambda function at intervals less than every 5 minutes while not using EC2 you can take a two phased approach. Create an AWS Lambda function to run every 5 minutes and have it actually run for the entire 5 minutes calling other AWS Lambda functions asynchronously at the appropriate time intervals.
This approach will cost you more since the first AWS Lambda function that runs for the entire 5 minutes will essentially be running continuously, but you can reduce the cost by having the smallest amount of RAM allocated.
UPDATE
CloudWatch Events now allow for schedules at a frequency of 1 minute. This means you can now schedule a Lambda function to run every minute and have it run for up to a minute to achieve sub-minute accuracy. It is important to note that scheduled events do not fire at the beginning of every minute so achieving exact sub-minute timing is still a bit tricky.
Don't poll with lambda or you are better off using EC2 if you expect to spend more time polling than performing work. Lambda charges by execution time and polling is costly.
You really need an event driven system with Lambda. What determines when you poll?
There is a simple hack though where you could use a setTimeout or setInterval.
i.e:
'use strict';
async function Task() {
console.log('Ran at'+new Date())
}
const TIMEOUT = 30000;
exports.handler = (event, context) => {
return Promise.all([
Task(),
new Promise(function(resolve, reject) {
let timeout = setTimeout(function() {
Task().then(() => {
clearTimeout(timeout)
})
}, TIMEOUT);
})
])
}