Reducing Latency: Pre-Warming Lambda Functions with EventBridge Rules
Tags: programming, aws, typescript, tutorial
Published: 2023-08-03 16:53:10

For any application, it’s important to have low latency times to ensure a fast and responsive experience for users. But, this is especially true for serverless applications; they need low latency times to ensure they’re responsive to requests, can easily scale to meet demand, and are able to remain cost-effective.

That’s why in this post we’re going to be taking a closer look at AWS Lambda and how cold-starts can cause increased latency and reduced performance as well as how we can mitigate these issues by keeping our lambda functions warm using EventBridge rules.

## Cold and Warm Lambda’s

But, first, what are cold and warm lambdas? A cold lambda is a lambda that hasn’t been loaded into the execution environment, this is typically because it has either not been invoked before or hasn’t been invoked for a significant period. This means when we go to invoke a cold lambda, AWS needs to set up the resources and infrastructure required to execute the function and it’s this setup that causes an increase in latency and response time.

It’s also worth remembering that the setup time can vary from function to function for several reasons but two of the most consequential reasons are the size and complexity of a function. More complicated lambdas that use more resources will take longer to set up and can often have setup times of multiple seconds. This might not seem a long time but for applications that require near-instantaneous responses like APIs, a few seconds is unacceptable.

So, now, we know what a cold lambda is, what about a warm one? A warm lambda is a lambda function that is already present in the execution environment because it’s either been executed recently or it receives frequent invocations. This means when we invoke a warm function, AWS doesn’t need to set up any new resources or infrastructure because it’s already in place from a previous invocation, this means we get a (typically) much lower latency and faster response time.

At this point, you might be asking how can I keep my lambda’s warm then. To which I’ll respond, there are a few ways you can keep a lambda warm but the one we’ll be focusing on in this tutorial is a technique called pre-warming. This is where we use recurring requests to invoke our lambda function to keep it in the execution environment and therefore warm. We’ll perform the pre-warming by using an EventBridge rule on a cron job that is set to trigger our function every 5 minutes.

## How to Keep a Lambda Warm

Now, we’re up to date with the theory behind warm lambda functions and why we might want to use them; let’s take a look at how we can implement them into a CDK project as well as the latency savings they give us.

So, inside a CDK project, we’re going to define two lambdas, one to be our cold lambda and one to be our warm one. You can define these lambdas using the below code.

```ts
// 1. Defining our Warm and Cold Lambda Functions
const warmLambda = new NodejsFunction(this, "WarmLambda", {
  runtime: Runtime.NODEJS_16_X,
  entry: "./resources/warm-lambda.ts",
  handler: "handler",
  timeout: Duration.seconds(30),
});

const coldLambda = new NodejsFunction(this, "ColdLambda", {
  runtime: Runtime.NODEJS_16_X,
  entry: "./resources/cold-lambda.ts",
  handler: "handler",
  timeout: Duration.seconds(30),
});
```

After defining our functions, we’re then going to define the EventBridge rule that will trigger our warm lambda function every 5 minutes with the below code.

```ts
// 2. Defining our CloudWatch Event Rule for our CRON job to run every 5 minutes
const rule = new Rule(this, "Rule", {
  schedule: Schedule.cron({
    minute: "*/5",
  }),
});

// 3. Defining our warm Lambda function as the target for our CloudWatch Event Rule, also pass through an event for us to use in the function to exit early
rule.addTarget(
  new LambdaFunction(warmLambda, {
    event: RuleTargetInput.fromObject({
      source: "aws.events",
    }),
  })
);
```

In this code we also pass through an event object to our warm lambda function, this is important because we’ll use this object to determine if the lambda was triggered by us or the EventBridge rule.

If the lambda was triggered by the EventBridge rule, we’ll return early from the function so the lambda is executing for as little time as possible to help reduce costs as we’re billed for the total execution time.

With our lambda functions and EventBridge rule defined, let’s finish off our example project by writing the code for our lambda functions. For this, create a new directory in the root of your project called `resources` and then inside it, create two files `cold-lambda.ts` and `warm-lambda.ts`, and then add the corresponding code below to them.

```ts
import { EventBridgeEvent } from "aws-lambda";

export const handler = (
  event: EventBridgeEvent<
    string,
    {
      source: string;
    }
  >
) => {
  // If triggered by the cron job, return early from the function
  if (event.source === "aws.events") {
    return;
  }

  console.log("Hi! I am a warm Lambda function.");
};
```

```ts
export const handler = () => {
  console.log("Hi! I am a cold Lambda function.");
};
```

## Testing Our Lambdas

With the code for our lambda functions added, we’ve finished all we need for our cold/warm lambda example so let’s deploy it by running `cdk deploy`. Then after our deployment has finished, let’s wait for 5/10 minutes for our warm lambda to be triggered a couple of times to be 100% sure it’s warm and loaded into the execution environment.

So, after we’ve waited for our warm lambda to warm up, let’s invoke both lambdas by using the AWS CLI and running the command `aws lambda invoke --function-name <LAMBDA_NAME> --invocation-type Event -` for both of our lambda functions, making sure to switch `<LAMBDA_NAME>` for the name of the lambda function.

Once you’ve invoked both of the lambda’s, let’s check out their latency times by heading over to CloudWatch in the AWS dashboard. Click into both of the lambda function’s logs, and you should find logs similar to those below but with different IDs.

```
Warm Lambda

c9509a8b-1534-4a1a-aee4-cfb94138c5aa INFO Hi! I am a warm Lambda function.
REPORT RequestId: c9509a8b-1534-4a1a-aee4-cfb94138c5aa Duration: 1.50 ms Billed Duration: 2 ms Memory Size: 128 MB Max Memory Used: 58 MB
```

```
Cold Lambda

7c0c7027-f70a-4f45-aa16-28c84d33f801 INFO Hi! I am a cold Lambda function.
REPORT RequestId: 7c0c7027-f70a-4f45-aa16-28c84d33f801 Duration: 16.80 ms Billed Duration: 17 ms Memory Size: 128 MB Max Memory Used: 57 MB Init Duration: 154.67 ms
```

The bit of these logs we’re interested in is the “Duration:” property on the bottom line, this gives us the total time the lambda was executing; including the startup time to prepare the environment for the lambda to execute.

For the cold lambda, we have a total duration of **16.80ms**, and for the warm lambda, a total duration of **1.50ms**. This means by pre-warming our environment we’ve achieved a reduction of **15.30ms** or **~90%** which might not seem a lot but considering the simplicity of the lambda functions we’re running that’s a massive time saving which will only get bigger as the time and complexity of our lambda’s increase.

## Considerations

So, now we’ve seen the potential of pre-warming our lambda’s, let’s take a moment to consider some of the considerations we might want to think about when deciding whether or not we want to implement pre-warming into our projects.

### Cost

It’s worth considering if keeping your lambda’s warm is a necessity for your project. This is because while the lower latencies are nice, there will be an increase in cost to account for the extra lambda invocations and execution time being used. Whether or not pre-warming your lambdas is worth the cost will likely depend on your project type. For example, for an API it would make sense as the low latency is a priority but for an automated data scraping function, it probably won’t make sense.

### Reliability/Inconsistency

While pre-warming our lambda functions with EventBridge rules will work most of the time, there is the chance that your lambda’s will go cold between invocations. This means some of your requests will have higher latencies while the execution environment is configured again.

So, if the possibility of this inconsistency isn’t acceptable for your project, you might want to look into something like [Provisioned Concurrency](https://docs.aws.amazon.com/lambda/latest/dg/provisioned-concurrency.html). This will guarantee several warm lambdas are always ready to use but this will also incur extra costs compared to the pre-warming technique.

### Monitoring/Adjustments

Because pre-warming is a technique for keeping lambda’s warm, not a guarantee they will keep warm. You will need to spend some time monitoring and adjusting your EventBridge rules to ensure they work optimally for your project.

This is because if the cron period is too long, your functions may go cold and require a cold start with higher latencies. But, if they’re too short, you might be executing Lambda’s unnecessarily, increasing your costs.

## Closing Thoughts

We’ve now reached the end of the post so to recap; we’ve looked at what are cold and warm lambda’s as well as why we might want to keep our lambda’s warm. We then looked into how we can use EventBridge rules on a cron job to keep a lambda warm and the latency improvements of doing so. Before finally, concluding the post by looking at some of the considerations we should think about when deciding if pre-warming lambda’s are right for our project or not.

I hope you found this post helpful and if you’d like to see the example project I used for this tutorial, you can [see it on my GitHub repository along with all of the other tutorial projects I’ve done.](https://github.com/conermurphy/cdk-tutorials/tree/main/pre-warming-lambdas-eventbridge-rules)

Thank you for reading

Coner