How to invoke an AWS Lambda function at the top of a minute using AWS Step Functions

This post has been inspired by shimo's post, Trigger precisely at hh:mm:00 with EventBridge and Lambda, on the AWS Community Builders Blog.

For context, shimo wants to run a Lambda function at exactly hh:mm:00. Or at least within a few hundred milliseconds. A more exact granularity could be achieved, but would often be an unnecessary over-optimization.

In the example, the target function goes into a sleep state and waits until its execution time to make sure the actual function code runs at the top of a minute.

I'm always a fan of cost effectiveness, and while the proposed solution is great, there is some opportunity to reduce the overall cost and - if running at a very large scale - make it more sustainable as well, because the Lambda function - even if in a sleep state - will keep running, unnecessarily consuming power and incurring costs.

This can be optimized using the following AWS Step Functions state machine.

AWS Step Functions state machine

The first step will invoke a Lambda function that takes the current time, rounds it up to the next full minute, and returns a JSON object containing the scheduled execution time:

# Timer function returns current time, rounded up to the next full minute

from datetime import datetime, timedelta

def lambda_handler(event, context):
  one_minute_from_now = datetime.now() + timedelta(minutes=1)
  execution_time = (one_minute_from_now).strftime('%Y-%m-%dT%H:%M:00.000')+'Z'

  return {
    'scheduledExecutionTime': execution_time
  }

Then the state machine will go into a wait state until the scheduled execution time has arrived. Here's the snippet defining the wait state, which takes the $.scheduledExecutionTime from the above function's response:

...
  "Wait": {
    "Type": "Wait",
    "TimestampPath": "$.scheduledExecutionTime",
    "Next": "Invoke Function"
  }
...

Once the scheduled execution time has arrived, the actual Lambda function will be invoked.

Let's have a look at the execution log:

State machine execution log

(1) The execution starts at 12:34:42.288 (GMT+1).

(2) The timer function returns the scheduled execution time, which is the start time rounded up to the next minute (minus the timezone offset, the AWS backend will automatically take care of timezone issues for you).

(3) The state machine takes the scheduled execution time, goes into the wait state for a little more than 17 seconds, and invokes the worker function at 12:34:59.352, which is almost exactly the top of the next minute.


Here's the entire state machine configuration:

{
  "StartAt": "Get Execution Time",
  "States": {
    "Get Execution Time": {
      "Type": "Task",
      "Resource": "arn:aws:states:::lambda:invoke",
      "OutputPath": "$.Payload",
      "Parameters": {
        "FunctionName": "TIMER_FUNCTION_ARN"
      },
      "Next": "Wait"
    },
    "Wait": {
      "Type": "Wait",
      "TimestampPath": "$.scheduledExecutionTime",
      "Next": "Invoke Function"
    },
    "Invoke Function": {
      "Type": "Task",
      "Resource": "arn:aws:states:::lambda:invoke",
      "OutputPath": "$.Payload",
      "Parameters": {
        "FunctionName": "WORKER_FUNCTION_ARN"
      },
      "End": true
    }
  }
}