5 ways to speed up your Lambda function

Enrico Portolan
Towards AWS
Published in
5 min readMay 20, 2022

--

Let’s face it, AWS Lambda is one of the first services you learn while trying Serverless and yet there are always new ways to tweak it.

Photo by Tj Holowaychuk on Unsplash

In this blog post, I will explain 5 methods to improve the execution time of your Lambda function. Why does it matter? For two main reasons:

  • Cost: You are charged for the execution time of your Lambda function (milliseconds granularity)
  • User Experience: the faster your Lambda the better the experience of your customers using the service

If you prefer to follow along by watching a video, feel free:

Reuse the Execution Context

We can leverage the execution context and reuse it to limit the re-initialization of variables and objects on every invocation. This is especially important if you have an externalized configuration or connection to a database. We can use a static constructor, static variables, and singletons.

Hold on, what is the execution context? 🤔

Lambda invokes your function in an execution environment, which is an isolated runtime environment. The execution context is a temporary runtime environment that initializes any external dependencies of your Lambda code. As your Lambda function can be invoked numerous times and scale, the execution context is maintained for some time in anticipation of another Lambda function invocation. When that happens it can “reuse” the context which will save time on your function.

Let’s see an example below:

module.exports.handler = (event, context, callback) => {    
var client = mysql.createConnection({
// your connection info
});
client.connect()
client.query('SELECT * FROM `products`',function (error,results) {
callback(null, results)});
}

As you can see, every time the Lambda function is executed, it creates a new connection to the MySQL database. We can rewrite the function to re-use the context:

const mysql = require('mysql');  
// If 'client' is null, connect to the databse
if (!client) {
var client = mysql.createConnection({ // your connection info });
client.connect()
}
module.exports.handler = (event, context, callback) => { client.query('SELECT * FROM `products`', function (error, results){
callback(null, results)
});
}

Optimize the external network calls

A very common use case is to include 3rd party API inside your Lambda function. The execution time of the 3rdparty API will increase the total Lambda execution time:

LambdaLocalTime + 3rd party API Response Time = Total Execution Time

How can we improve this? For example, enable TCP Keep-Alive and reuse connections (to HTTP, databases, and so on) that you established during previous invocations.

In addition, when possible, make network calls to resources in the same region where your Lambda@Edge function is executing to reduce network latency.

Optimize your deployment package

Avoid dependencies on external packages for simple functions that you can write yourself. When you need to use an external resource, choose lightweight packages. In addition, use tools like minify to compact your deployment package.

It’s worth mentioning that Lambda has already installed some libraries such as aws-sdk. Set aws-sdkas a dev dependency, remove unnecessary packages and deploy your function 🚀

Tuning Memory and CPU

AWS Lambda provides memory ranges from 128 MB to 3,008 MB in 64 MB increments. Although we only specify the RAM, a linearly proportional amount of CPU power gets allocated to the Lambda Function by AWS.

How can you balance memory and Lambda execution time?

As we know, Lambda costing depends on both memory allocation and execution time. If we need to reduce the Lambda execution time, we can try increasing memory (and by extension, CPU) to process it faster. However, when we try to increase the memory for a function past a certain limit, it won’t improve the execution time as AWS currently offers a maximum of 2 cores CPU.

If your application leans more towards computation logic (i.e. it’s CPU-centric), increasing the memory makes sense as it will reduce the execution time drastically and save on cost per execution.

Also, it’s worth paying attention to the fact that AWS charges for Lambda execution in increments of 1ms. So, every ms saved can impact the overall bill. Sometimes it will happen that you’re getting faster functions, for the same price 🏎

But do note, that the results may vary depending on the task the function is performing. For example, in some cases, you might not achieve a reduction big enough for the price reduction to happen.

Before bumping up RAM, do test your function with different payloads, and then based on the results, determine if there are any actions worth taking.

The go-to open-source tool is called aws-lambda-power-tuning.

Lambda Provisioned Concurrency to Address Cold Start Performance Problems

There are two types of concurrency controls available:

  • Reserved concurrency — Reserved concurrency guarantees the maximum number of concurrent instances for the function. When a function has reserved concurrency, no other function can use that concurrency. There is no charge for configuring reserved concurrency for a function.
  • Provisioned concurrency — Provisioned concurrency initializes a requested number of execution environments so that they are prepared to respond immediately to your function’s invocations. Note that configuring provisioned concurrency incurs charges to your AWS account (it’s not real Serverless 😛).

We will focus on the latter one, provisioned concurrency.

When Lambda allocates an instance of your function, the runtime loads your function’s code and runs the initialization code that you define outside of the handler. If your code and dependencies are large, or you create SDK clients during initialization, this process can take some time. When your function has not been used for some time, needs to scale up, or when you update a function, Lambda creates new execution environments. This causes the portion of requests that are served by new instances to have higher latency than the rest, otherwise known as a cold start.

By allocating provisioned concurrency before an increase in invocations, you can ensure that all requests are served by initialized instances with low latency. Lambda functions configured with provisioned concurrency run with consistent start-up latency, making them ideal for building interactive mobile or web backends, latency-sensitive microservices, and synchronously invoked APIs.

Conclusion

I hope you will find this blog post useful and it will help you to execute your Lambda functions faster and save some money 💰

Let me know what you think in the comments. Follow me on Twitter and Youtube for more!

--

--

Passionate about cloud, startups and new technologies. Full-stack web engineer