avatarBogdan Vaduva

Summary

The article discusses strategies to mitigate AWS Lambda cold starts, focusing on keeping lambdas warm and using interactors to consolidate multiple lambda functions into a single one.

Abstract

In the context of AWS Lambda functions, the article addresses the challenge of cold starts, which can significantly delay response times for end-users. It explains that a cold start occurs when a Lambda function is invoked after being idle, leading to longer execution times compared to warm runs. The author presents two main strategies to reduce the impact of cold starts: periodically invoking lambdas to keep them warm, and implementing an interactor pattern to combine multiple lambda functions into one, thus maintaining a consistently warm state due to higher traffic. The interactor pattern involves a single lambda function that uses a class or set of functions to handle different types of requests based on the incoming event's HTTP method, which can simplify the architecture and potentially reduce costs associated with keeping multiple lambdas warm.

Opinions

  • The author suggests that the interactor pattern is a more comfortable and potentially better choice than keeping multiple lambdas warm, as it can lead to a more efficient and cost-effective system.
  • There is an acknowledgment that while keeping lambdas warm is a common approach, it can lead to unnecessary costs, especially with additional services like AWS Lambda Insights.
  • The interactor approach is preferred for its ability to serve multiple endpoints with a single lambda function, reducing the complexity and potential cold starts.
  • The article notes that the interactor pattern might not be suitable for all systems and that its effectiveness depends on the specific architecture and requirements of the system.
  • The author mentions that although the interactor pattern can become complex, it can be optimized by splitting the logic appropriately to balance the number of lambda functions and interactors used.

How to avoid AWS lambda cold start

In this short article we are going to talk a little bit about how you can avoid the cold starts of lambdas in AWS.

Reasoning

Lambda cold starts can be tricky. Assuming you have a system that uses lambdas to do its job, there might be cases where the actual lambda run takes under 1 second but the cold start takes up to 5 seconds, meaning that instead of getting your feedback from the system in about 1 second, you get it in approximately 6 seconds which sometimes it might be a little annoying, especially for users that use an UI to interact with your system.

Presuming I press a button in your app that should give me information about a specific entity, I would expect it to take about 1 second (and it might if the lambda is warm), sometimes it takes up to 6 seconds, which is a long time for a real-time system to respond.

Assumptions

  • code is written in python
  • you have basic knowledge of AWS Lambda, AWS APIGateway
  • basic REST API knowledge

Prerequisites

So let’s assume that we have a system that uses APIGateway to create an API and for each endpoint of that API, you have a lambda function that responds to incoming requests. We are going to assume that our API has only 3 endpoints, and they’re all related to the same entity, and since I like dogs, we’re going to be getting, adding or deleting dogs from a database. Here’s a diagram to showcase that.

So our API looks like this:

  • GET /dogs/{dog_id} → so you can get a dog from our database base on its entity ID
  • POST /dogs → so you can add a dog to our database
  • DELETE /dogs/{dog_id} → so you can delete a dog from our database, based on its id

As you can imagine, the code from the lambdas will be pretty much similar, especially if we use an ORM (object relational mapper) to work with the database.

Here’s how getting a dog would look like, keeping in mind that I am presuming you have a basic idea of how a lambda works, how the event in a lambda looks like and I am not dealing with error handling in this piece of code.

def handler(event, context):
  dog_id = event.get("dog_id")
  # Considering that Dog is an ORM model
  # And that a get on it returns a dictionary like object
  dog_object = Dog.get(dog_id)
  return dog_object

Similarly, for the addition of a dog, we would have some information in the event that is incoming such as color, age, weight, etc., and we will insert that into our database. For the deletion of a dog, the code is almost identical with us simply using the delete method instead of get from the ORM model.

Solutions

Keeping the lambda warm

One solution that I have seen applied is to “keep the lambda warm” which basically means that from time to time (usually it’s 15minutes, the maximum amount of time that a lambda can run), you call that lambda such that the container that the lambda is stored in and is running is also kept alive and the resource do not get de-allocated.

This of course come with its drawbacks, since you will be doing a lambda call once every 15 minutes at least, which leads to at least 96 lambda calls extra per day for you for just one lambda, because in a 24h day, you would have 4 lambda calls every hour. This will inevitably lead to more cost, especially if you also have attached to your lambdas other AWS services such as lambda insights, which tends to increase the costs quite a bit with more lambda calls.

Alternatively, you could do something in-between, maybe call the lambda every hour, reducing the number of extra calls to just 24 in a day.

Again, I have seen systems that use this approach, but I am not saying you should do it, in the end it all depends on the way your system is built.

Interactors

One approach which I have seen working and I feel more comfortable in choosing is to have an interactor

An interactor is a class, or set of functions, that takes in the incoming event and decides what to do with it within a lambda.

As an example, for our system, we would create an interactor for the 3 endpoints, and we will have only one lambda serving all 3 endpoints. The interactor code should look a little something like this:

class Interactor:
  
  def __init__(self, event):
    self.event = event

  def get_dog(self):
    # Code to get the dog from the database and return it
    pass

  def create_dog(self):
    # Code to create the dog and add it to the database
    pass

  def delete_dog(self):
    # Code to delete the dog from the database
    pass

  def run(self):
    # We are assuming that we have an integration within our infrastructure
    # that is passing the HTTP method in the event
    if self.event.get("httpMethod") == "GET":
      self.get_dog()
    elif self.event.get("httpMethod") == "POST":
      self.create_dog()
    elif self.event.get("httpMethod") == "DELETE":
      self.delete_dog()

def handler(event, context):
  # We create the interactor entity
  interactor = Interactor(event=event)
  # we let it run, choosing by itself what needs to be done depending
  # on the incoming event
  interactor.run()

What this will do for us is that now, we will not have 3 lambdas responding to 3 different types of requests, but only 1 lambda responding to 3 types of requests, which means that in theory, it will be kept warm by itself (of course, there might be cases when it will still need the cold start), but all the calls that are done are calls that NEED to happen for the system to do its job, and not calls that are just supposed to keep the lambda warm.

Now your infrastructure should look a little something like this:

Now, there might be a case when an interactor might become way more complex than you wish it to become, and that is one of the drawbacks of using this approach. But you could come up with better splits of the logic to have instead of 10 lambdas, just 2 with interactors, so the solution can be changed a bit to suit your needs.

Conclusion

There might be other solutions to this, some of them even more elegant and maybe easier to implement, but these two I have seen used quite a lot in live system and they do work, provided that you take into account their drawbacks.

Happy coding!

AWS
AWS Lambda
Amazon Web Services
Python
Serverless
Recommended from ReadMedium