MongoDB e AWS Lambda: desafios no gerenciamento de conexões

A utilização de funções AWS Lambda tem sido cada vez mais frequente para proporcionar uma infinidade de soluções.

            Na maioria das vezes, o uso de recursos serverless está associado a uma redução da complexidade de gerenciamento de infraestrutura e automatização na escalabilidade. Mesmo assim, é muito importante conhecer o comportamento de sua aplicação e saber os limites das tecnologias utilizadas, para não haver surpresas indesejadas no futuro.

            Estabelecer e gerenciar a conexão entre o MongoDB e funções AWS Lambda pode não ser tão trivial quanto se imagina. Cada driver de conexão possui peculiaridades, que devem ser observadas na integração do banco de dados com qualquer recurso. 

AWS Lambda e conexões via MongoDB C# driver

            Nas Lambdas com conexão direta ao MongoDB, sempre que a instância é invocada pela primeira vez uma nova conexão com o banco de dados é criada. Até esse ponto tudo corre como esperado, os problemas começam a surgir quando existe a necessidade de fechar essas conexões, pois a Lambda a manterá com o banco para atender uma nova requisição ou atingir o tempo limite para a sua efetiva destruição por ociosidade, o que pode levar até 15 minutos para ocorrer.

            O mais preocupante é que mesmo após a “destruição” da Lambda a conexão com o MongoDB não é imediatamente fechada, ela continua aberta mesmo sem uso, criando uma verdadeira conexão “zumbi”. Eventualmente, o servidor do MongoDB poderá se livrar dessa conexão a depender de sua configuração, mas isso não será abordado neste momento.

            A documentação oficial do MongoDB sugere que as conexões sejam reutilizadas o máximo possível, contudo, os critérios para reutilização de uma instância Lambda possuem uma dinâmica própria e dificilmente estarão alinhados com as necessidades de conexão com o banco de dados. Além disso, não é mais possível (de forma ortodoxa) fechar a conexão programaticamente utilizando o driver oficial do MongoDB/C#. Essa funcionalidade foi removida em versões > 2.0 por uma decisão de design em favor do reuso de conexões.

AWS Lambda e conexões via MongoDB C# driver

            Para ilustrar esse cenário, basta imaginar uma Lambda que se conecta diretamente ao MongoDB para fornecer informações para certa aplicação. Se em dado momento essa Lambda receber um número alto de conexões, consequentemente inúmeras instâncias concorrentes serão criadas para a função suprir essa demanda. Contudo, essas mesmas instâncias, mesmo após responder às requisições, continuarão com uma conexão ativa com o MongoDB, até que seu tempo de vida se esgote e finalize a conexão com o banco de dados de forma adequada.

            O número de instâncias Lambda pode ser infinitamente maior que o número de conexões possíveis no MongoDB. Consultando a documentação da AWS é possível verificar que uma única Lambda pode gerar entre 1000 e centenas de milhares de execuções concorrentes.

Por outro lado, o número de conexões disponíveis no MongoDB ou DocumentDB (Banco NSQL da AWS compatível com MongoDb) são muito mais modestas:

Database Min Connections / Cluster Size Max Connections / Cluster Size
MongoDB Atlas 500 / M0 128000 / M300
AWS DocumentDB 500 / T3.medium 30000 / R5.24xlarge

            Por isso, é bastante comum implementações inocentes do MongoDB com Lambdas .NET causem um rápido esgotamento das conexões disponíveis no banco. Mesmo o cluster mais caro e potente do MongoDB Atlas pode ter suas conexões esgotadas rapidamente por Lambdas de acesso intensivo.

            O número de instâncias Lambda pode crescer abruptamente, criando novas conexões com o banco que mesmo sem qualquer novo uso só diminuem após o tempo de vida das Lambdas chegar ao fim, ou seja, pode levar até 20 minutos para o banco ter novas conexões disponíveis. Um verdadeiro pesadelo.

Recomendações e possíveis soluções

Configure funções Lambda conforme a sua finalidade

            Inicialmente, é preciso afastar a ideia de que é possível escalar elementos em um ambiente serverless sem que isso gere consequências. Uma Lambda pode ser colocada em produção em minutos, mas isso não afasta a necessidade de entender a sua finalidade e configurar os seus limites de acordo com a sua função.

Se a função Lambda possui conexão direta com o MongoDB estas recomendações podem evitar vários problemas:

  • Defina o client do servidor MongoDB fora do escopo da função Lambda. Isso previne que a conexão seja reiniciada a cada invocação e possibilita o reuso.
  • Caso sua aplicação utilize injeção de dependência, registre o client como Singleton. O client disponibilizado pelo Mongo/C# driver é thread safe.
  • Não utilize o limite de concorrência padrão para a Lambda, uma vez que o limite padrão é de 1000. Estipule um valor que se encaixe dentro das necessidades da Lambda e respeite os limites do seu servidor do MongoDB.
  • É possível ajustar esse valor no painel de configurações da Lambda em Concurrency -> Edit -> Reserved Concurrency

Criar um proxy de conexão

            Pensando nesse problema, a AWS disponibiliza para seus bancos RDS uma ferramenta de proxy que maneja o gerenciamento de conexões e se apresenta como uma solução para acesso aos bancos em ambiente serverless. Ocorre que até o momento (08/2021) essa solução não contempla bancos NoSQL como o MongoDB/DocumentDB.

            Se várias Lambdas necessitam de acesso ao banco, considere criar uma função que atue como proxy de conexão, atuando como interface de acesso ao banco, centralizando configurações próprias para esse tipo de atividade.

            Esses são somente alguns cuidados que podem evitar grandes problemas no uso do MongoDB em ambientes serverless.

Referências

[MongoDB Atlas Limits]

https://docs.atlas.mongodb.com/reference/atlas-limits

[AWS DocumentDB Limits]

https://docs.aws.amazon.com/pt_br/lambda/latest/dg/gettingstarted-limits.html

[AWS Lambda quotas]

https://docs.aws.amazon.com/lambda/latest/dg/gettingstarted-limits.html

[MongoDB .NET Driver]

https://mongodb.github.io/mongo-csharp-driver/2.13/getting_started

Post a Comment

* indicates required