Tuning de conexiones en MongoDB con Serverless Lambda
Tags: aws, mongodb, node
Published: 2024-05-10 11:55:12

## Contexto
Luego de una etapa de gestión y mantenimiento manual de una base de datos _MongoDB_ (nosql) instalada _on premise_, se tomó la decisión de comenzar a usar el servicio cloud [_Mongo Atlas_](https://cloud.mongodb.com/v2/65bb8a1ba4a8dd3d7853256a#/overview). Esto se debió a que el mismo ofrece múltiples ventajas -de las cuales “replication” es la más destacable, ofreciendo la posibilidad de implementar la base de datos en un _cluster_ de nodos de forma rápida y eficiente. También ofrece buenas herramientas para monitorear tanto la escalabilidad de la base de datos como los estados del _cluster_.

![Mongo Atlas Monitor](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ufc0221m2iph0fqbgva2.png)

## Problema

Al comenzar a utilizar _Mongo Atlas_, se observó que crecía exageradamente el número de conexiones concurrentes abiertas –alcanzando, por ejemplo, picos de **100** conexiones con un test de carga de solo 5 usuarios simultáneos. Esto derivó en dos escenarios de error:

- Escenario en el que el backend ([_AWS Lambda_](https://aws.amazon.com/es/lambda/)) terminaba requests por _timeout_ debido a problemas de conexión.
- Caída de la base de datos en caso de haber superado el límite de conexiones de ese momento (**500**).

## Diagnóstico

Tomando como referencia a la documentación de [_Mongoose_](https://mongoosejs.com/docs/5.x/docs/connections.html) y [_Lambda_](https://www.serverless.com/framework/docs/providers/aws/guide/functions) se encuentra que, al estar acotado el parámetro _poolSize_ de las conexiones a la BD (por defecto **5**) y al mismo tiempo no definido un tope para la cantidad de containers (instancias) de _Lambda Functions_ que AWS levanta / crea _on-demand_, **AWS está levantando containers arbitrariamente según el tráfico y requests que recibe el servicio** (a través de [_API Gateway_](https://aws.amazon.com/es/api-gateway/)). 

Por otra parte, debido a que la API está desplegada en una sola _Lambda_ (donde internamente corre un API [_Express_](https://expressjs.com)), todos los requests a diferentes recursos los gestiona un único _container_. Por ende, este recibe varios _requests_ concurrentes y todos son atendidos por un único _pool_ de conexiones. Dicho de otra manera, cada instancia de _Lambda_ podrá procesar únicamente **5** operaciones concurrentes con acceso a base de datos.

Debido a que AWS no tiene predefinido un tope límite de instancias creadas, estas son creadas a medida que son necesarias para de esta manera “balancear” el tráfico. Por ese motivo, y ya que por cada _container_ Mongoose está creando un _pool_ de **5** conexiones: si AWS levanta 20 _containers_ vivos, levantamos con **100** conexiones en _Mongo Atlas_, **exponiéndolo a la condición de saturación con la que nos encontramos**.

Alcanzar el límite de conexiones del _cluster_ puede afectar el rendimiento y la disponibilidad de la base de datos o, en el peor de los casos, generar una caída en el servidor, requiriendo un reinicio manual.

## Solución

Ya que el _pool_ de conexiones al _cluster_ es proporcional a la cantidad de _containers_ creados por AWS, se decidió poner un límite al número de _containers_ vivos que pueden existir. Para este caso definimos un máximo de **20** instancias a través de la opción [_reservedConcurrency_](https://docs.aws.amazon.com/lambda/latest/operatorguide/reserved-concurrency.html) en nuestro archivo `serverless.yml` para configurar el _serverless_ framework.

```yml
functions:
   <api_name>:
      reservedConcurrency: 20
      timeout: 20
      memorySize: 1536
```

Asimismo y tomando en consideración este parámetro buscamos un valor para _poolSize_ que nos permitiera tener el mayor número de conexiones en el _pool_ disponible sin superar el límite del cluster: _20 x 24 = **480**_ parece un escenario más conservador y, al mismo tiempo, más eficiente en el uso de los recursos. De la siguiente manera configuramos la conexión a la base desde nuestra API _Express_:

```javascript
const db = await.mongoose.connect(mongodbUri, {
   poolSize: 24 
});
```


> Otra configuración viable podría ser 30 x 16; léase, **30** instancias máximo de containers Lambdas x **16** conexiones reservadas en pool.

## Monitoreo y conclusión

Luego de las medidas tomadas, no se observaron picos en la apertura de conexiones con lo cual se concluye que la solución fue efectiva.

Es igualmente recomendable darle un seguimiento al _performance_ del _cluster_ de la base de datos, observando las estadísticas de conexiones y gestionando alertas para casos en los que dicho número de conexiones se acerque a las **480**.

En lo que concierne al API, es a su vez recomendable observar y buscar errores _**5xx**_ dentro de _API Gateway_ y configurar alertas en caso de que sean persistentes en un corto período de tiempo (_downtime_).

---

Quiero agradecer a @frangeris  por su colaboración y aportes en el diagnóstico y la construcción de una solución acorde al escenario en cuestión, así como también en su documentación posterior.

Imagen del encabezado tomada de Nick Van Hook (2020). [_"Marrying Mongodb Atlas and AWS Lambda."_](https://blog.ordina-jworks.io/cloud/2020/02/19/Combining-MongoDB-and-AWS-Lambda.html)