DEV Community

Cover image for Reuse Redis Connections across AWS Lambda invocations
Afraz Khan
Afraz Khan

Posted on • Updated on

Reuse Redis Connections across AWS Lambda invocations

If you are familiar with the lambda execution environment and how it works for a single lambda function invocation, then it's a great opportunity for you to optimise your application's performance. The lambda execution environment enables you to persist and reuse DB connections, especially in situations where latency must be taken into account and connection open/close at the same time is not desirable.
πŸ‘‰ Lambda Execution Environment

Recently, I have implemented a solution for reusing Redis client connections across the invocations of a lambda function in a single lambda execution environment, I will share that here today.

Note that examples are given using TypeScript and
this blog could even help you to reuse your database connections (MySQL, PostgreSQL, etc.).

1. Make use of Lambda Static Initialization

The code placed outside of function's main handler method is run only when execution environment is spinned up for the first time. Lambda keeps that code as long as environment is warm.
πŸ‘‰ Static Initialization

  • Initialize a global variable outside the handler method that saves the Redis client object.

lambda-init-example

You can put this global Redis client variable in any file as per your function's code structure. Its not necessary to keep it in the handler method file.

2. Setup Idle Connection Timeout in Redis/database

Since, we want to reuse the database connections so, no need to end newly created connections.

  • Hence, delete the connection-close logic from your lambda function. If you have multiple usecases in your lambda function then do it only for those where you want to reuse the existing connection.

Note that, once a lambda execution environemnt is taken down, these opened connections become useless and they can take up as much memory to cause OUT OF MEMORY errors in Redis/database server.

  • Configure an idle connection timeout in Redis.(or in your database service) to terminate these idle connections automatically.

    πŸ‘‰ Redis Client Handling

    You need to be careful here, analyse your application requirements and configure a timeout in Redis as per your need.

    It shouldn't be larger than the lambda execution environment idle timeout which lies somewhere between 40-50mins. I found this stat online but there's no proper evidence about it. You may wanna perform tests to find it yourself :)
    Also, it shouldn't be very small at the same time, otherwise, your lambda function might create new connections as frequently as it matches the previous rate and this whole effort becomes pointless.

3. Update Redis Client Creation Logic

You need to update the client creation logic based on two things:

  • If existing client is active and working then function uses that instead of creating a new one.
  • Client would be reset if existing connection is terminated by Redis/database engine in the wake of connection idle timeout.

There are multiple ways to manage it:

  1. Create a separate method called getRedisClient() as shown in the below figure. Keep the client reuse/reset logic in that method and only use it throughout the lambda function for fetching an active Redis client. generic-mehtod This approach is cleaner and maintainable. If your application can bear the extra ping() call, then it is a good solution.
  2. Here, move the client reset logic into the API methods like below. It saves you the ping() call, so it is more performant. api-method This approach is favorable when your lambda function is doing only one task. If you have multiple business logic operations in your function, then maybe not a good option. You will have to insert the client reset logic in all those methods where Redis operations are performed. Not good for clean code principles.
  3. Implement a Global Error Handler in the lambda function or a pattern like that. See if lambda retry mechanism helps for that. Basiaclly, idea is to transmit all errors to that error handler where the Redis/database client is reset as done in the second approach.

    This looks like a perfect solution that avoids the extra ping() call in the first approach and the repetition pattern in the second approach.

I chose the first approach as it suited my application. The third option is just an idea that I didn't experiment with cz I am busy with other thingsπŸ₯². Maybe you can give it a try and let me know :)


After updating the Redis client creation logic, your lambda funtions are all setπŸš€ to reuse the existing connections.

Top comments (1)

Collapse
 
nrathi profile image
Neha Rathi

For folks using ioredis, the solution looks like this:

const MAX_RETRIES = 10;
const MAX_DELAY_MS = 5000;

const client = new Redis({
host: process.env.REDIS_HOST,
port: process.env.REDIS_PORT,
retryStrategy(times) {
if (times > MAX_RETRIES) {
return null;
}
return Math.min(times * 100, MAX_DELAY_MS);
},
});

When the connection is lost, ioredis automatically retries, so you just need to specify the strategy.