Is there a way to trigger an AWS Lambda on a dynamic timer? Currently, I am utilizing scheduled-events to trigger the lambda, but this is a set timer. Is there a way to dynamically set a time for the Lambda to be triggered from within the Lambda?
The idea here is that this Lambda does specific checks and executes code to know when it should run next (because I only want this lambda to run when it needs to). I want to 1) determine the next time it needs to run and 2) set the time from within the Lambda code.
I see there are a lot of resources that are used for triggering Lambda functions (SNS, Kinesis, etc.), but I cant seem to find a good way to dynamically kick one off.
This can be accomplished by setting a CloudWatch event rule to trigger your Lambda function. On each invocation of your Lambda function, the function will need to determine its next run time and modify the event rule appropriately.
var AWS = require("aws-sdk");
exports.handler = function(event, context) {
var cloudwatchevents = new AWS.CloudWatchEvents();
var intervals = Array(3, 5, 7);
var nextInterval = intervals[Math.floor(Math.random()*intervals.length)];
var currentTime = new Date().getTime(); // UTC Time
var nextTime = dateAdd(currentTime, "minute", nextInterval);
var nextMinutes = nextTime.getMinutes();
var nextHours = nextTime.getHours();
// =================================
// DO YOUR WORK HERE
// =================================
var scheduleExpression = "cron(" + nextMinutes + " " + nextHours + " * * ? *)";
var params = {
Name: "YOUR CLOUDWATCH EVENT RULE NAME",
ScheduleExpression: scheduleExpression
};
cloudwatchevents.putRule(params, function(err, data) {
if (err) {
console.log(err, err.stack);
}
else {
console.log(data);
}
})
};
var dateAdd = function(date, interval, units) {
var ret = new Date(date); // don't change original date
switch(interval.toLowerCase()) {
case 'year' : ret.setFullYear(ret.getFullYear() + units); break;
case 'quarter': ret.setMonth(ret.getMonth() + 3*units); break;
case 'month' : ret.setMonth(ret.getMonth() + units); break;
case 'week' : ret.setDate(ret.getDate() + 7*units); break;
case 'day' : ret.setDate(ret.getDate() + units); break;
case 'hour' : ret.setTime(ret.getTime() + units*3600000); break;
case 'minute' : ret.setTime(ret.getTime() + units*60000); break;
case 'second' : ret.setTime(ret.getTime() + units*1000); break;
default : ret = undefined; break;
}
return ret;
}
You should be able to swap my random determination with your own scheduling logic and insert whatever work you need in place of my comment.
You will need to substitute your event rule's name for "YOUR CLOUDWATCH EVENT RULE NAME" in my snippet.
Great question for a blog: AWS Lambda Functions That Dynamically Schedule Their Next Runtime
This can now be accomplished without polling using a step function. You can find more information on AWS, but basically you would define a state machine for your step function that uses the Wait
state and the TimestampPath
field. Your state machine might end up looking something like
{
"SartAt": "WaitState",
"States": {
"WaitState": {
"Type": "Wait",
"TimestampPath": "$.timestamp",
"Next": "ExecuteLambda"
},
"ExecuteLambda": {
"Type": "Task",
"Resource": "lambda-arn",
"End": true,
"Retry": [
{ ... },
...
]
}
}
Assuming you're using Node, you could then invoke the step function with the following code:
const AWS = require('aws-sdk');
const stepFunctions = new AWS.StepFunctions();
await stepFunctions.startExecution({
stateMachineArn: process.env.STATE_MACHINE_ARN,
name: "unique-name",
input: JSON.stringify({
timestamp: (new Date(/*Date you want to start execution*/)).toISOString(),
// Any extra context you want to pass to the step function.
}),
}).promise();
You could create a CloudWatch rule to run at a particular time. CloudWatch rules can be run periodically or with a cron like syntax that lets you specify a single run time. You'll likely need to clean these up later though.
You can keep executing the lambda on a schedule but persist a value in a data store such as DynamoDB or S3 that tells the lambda when it should run next.
The lambda will keep executing periodically but you can control when it actually does what its intended for. There are billing considerations here (the lambda will continue consuming a minimal amount of resources in the background) but hopefully things shouldnt get too out of hand. Probably simpler than trying to manage the triggers from within the same lambda.