As you may know, Lambdas are pieces of code that are executed by a runtime in a cloud provider as a self-managed service that does not require the administration of a server (virtual machine) instance. To mention some of these cloud services, there are AWS Lambdas, Google Functions and Azure Functions. Commonly, these services have been used to create API’s with the purpose of providing a REST endpoint. There are other use cases like websockets or cron jobs. But for this short article, I’ll talk about REST endpoints.
A Lambda function executes procedural code in four steps
So, what happens if you have a database connection and observability services on the second step of preparation? If those resources use execution time on every request that affects response time.
Let me tell you about my experience with it. My team had a database connection and a GraphQL server with Apollo Server. So for every request we re-create the database connection and also pre-build an Apollo Server instance. We did that to get some flexibility, but the approach doesn’t justify that we forgot an important feature about AWS Lambdas life cycle execution environment. In summary when a request process ends, the runtime anticipates and awaits for a new invocation, caching instances saved in global context to be reused on the next requests. This means that adding a code check for not creating a new instance when it already exists on cache, allows optimization when the function is invoked again.
We found that Lambdas have a short life cycle before freezing the code execution, so it’s possible to cache some objects in a global context.
The Lambda is alive for some minutes (15–40 minutes.) After that, the Lambda dies if there's no request in the queue. So, you are able to re-use some of the database connections and instances. This speeds up the process for the request almost over 50% for every request in our use case.
Finally, a good proposal for the last Lambdas could be like the next code example. The variable connection is a global variable inside the lambda context. The handler function implements a check if the variable has been previously assigned a connection instance, and only creates a new connection if is not defined. At adding this logic the await on databaseConnection.connect()only affects the first invoke function. The next invocations reuse the connections and optimize the lambda response time.
Applying this pattern is very close to the singleton pattern. With this, we handle the heavy task for the cold-start in the Lambda function. For the next requests, the heavy process is cached in the runtime memory. So the response time is reduced to a minimum and you are ready for a production release. That’s all, folks. Thank you for reading!
We’d love to learn more about your project.
Engagements start at $75,000.