Serverless: desafios e aprendizados de uma aplicação altamente escalável

Thomas Gustavo Dietrich
CWI Software
Published in
5 min readMar 10, 2020

Recentemente nós criamos um novo framework backend para nossas aplicações web, voltado totalmente para ser utilizado na arquitetura serverless, com AWS Lambda. Se tomados os devidos cuidados, o custo de infra em uma aplicação com backend serverless pode ser muito menor se comparado à utilização de máquinas virtuais. Além disso, com esse tipo de arquitetura, basicamente não precisamos ter preocupação com auto escalonamento. Como as aplicações que desenvolvemos precisam ser capazes de suportar um grande aumento no número de usuários em alguns segundos, a utilização de AWS Lambda basicamente elimina a necessidade de criarmos um gerenciamento de auto escalonamento, necessário quando utilizamos máquinas virtuais.

No backend dos projetos antigos utilizávamos Java Spring e as aplicações rodavam em Tomcat, instalados em instâncias EC2, e utilizando os recursos de alta disponibilidade e auto escalonamento da AWS. O novo framework que criamos utiliza Python 3.7, Flask, e o deploy da aplicação é feito no AWS Lambda utilizando Zappa. Para disponibilizar os endpoints para acesso externo, o Zappa utiliza o API Gateway.

Desenvolver aplicações serverless requer cuidados bastante diferentes do que tínhamos utilizando a arquitetura antiga. Listo aqui alguns dos detalhes que tivemos de dar atenção durante o desenvolvimento desse novo framework.

Tamanho do pacote final da aplicação

Quando utilizamos Java, é comum que o arquivo war ou jar seja bastante grande, podendo chegar aos 200 MB dependendo das bibliotecas utilizadas. Na arquitetura serverless, quanto maior o pacote, maior o tempo de cold start. Então, sempre que adicionamos uma nova biblioteca à aplicação, devemos tomar o cuidado de verificar o quanto o pacote da aplicação irá aumentar com essa biblioteca. Em alguns casos optamos por desenvolver uma solução própria para resolver algum problema ao invés de importar uma biblioteca. Um exemplo foi a biblioteca pycryptodome. Ela aumenta o tamanho do pacote em 10 MB. Sem ela, nossas aplicações costumam ter por volta de 6 MB, e quando importamos ela, aumenta para 16 MB. Isso resulta em um cold start em torno de 2 segundos maior.

Conexão com banco de dados e serviço de cache

Outra diferença é a respeito da conexão com banco de dados e serviço de cache — no nosso caso, MySQL e Redis. Diferentemente do serverless, na arquitetura antiga a aplicação ficava 100% do tempo sendo executada, independente de ter usuários ou não acessando ela. O Java mantém um pool de conexões com o banco de dados sempre ativo. Quando a aplicação necessita uma conexão, é só pegar uma do pool e utilizar. O Spring controla tudo isso para o desenvolvedor. Já na arquitetura serverless, a aplicação só roda quando um usuário acessa. Ou seja, não existe pool de conexões, todas as execuções que utilizam banco de dados devem capturar a conexão no início da execução e liberá-la ao final. Se ela não for liberada, a conexão ao banco ficará ocupada pelo container Lambda, que é congelado ao final de cada execução. E se muitos usuários acessarem a aplicação, corre-se o risco de atingir o limite de conexões definidos pelo banco de dados. A conexão com o Redis funciona da mesma forma: deve-se abrir uma conexão quando necessário e sempre liberá-la ao final da execução.

Necessidade do uso de um serviço de cache

A utilização do Redis se faz necessária por motivos semelhantes à da arquitetura anterior. Como a aplicação era executada simultaneamente em no mínimo duas instâncias, dados em memória, como sessão de usuários, não poderiam ser salvos na própria instância, e para isso utilizamos o Redis. No caso da arquitetura serverless, uma execução é independente da outra. Então se, por exemplo, quando o usuário entra na aplicação web, duas chamadas ao backend são feitas, ambas são totalmente independentes. Se dados em memória devem ser compartilhados entre as duas chamadas, deve-se salvá-los neste serviço externo.

Limites de execuções do AWS Lambda

Utilizar serverless simplifica bastante o auto escalonamento. Basicamente isso já faz parte da arquitetura e não é necessário utilizar nenhum serviço de escalonamento, como o Auto Scaling Group da AWS. Então, se apenas um usuário está utilizando a aplicação, apenas um container Lambda está sendo executado. E a medida que mais usuários entram na aplicação simultaneamente, mais containers são iniciados para suportar o tráfego. O principal detalhe aqui é que a AWS possui um limite de 1000 containers por região. Isso requer cuidados especiais para caso a aplicação tenha um tráfego bastante alto. Para diminuir a chance de atingirmos esse limite, estamos utilizando o CDN Cloudfront entre o API Gateway e o usuário. Dessa forma, configuramos para que os endpoints da aplicação retornem header cache-control indicando algum tempo de cache. Assim, conseguimos fazer cache destas informações no CDN e no browser do usuário, o que evita chamadas ao Lambda. Os únicos endpoints que não possuem nenhum tipo de cache são os que trafegam dados específicos do usuário logado — estes retornam o header cache-control com o valor max-age=0, fazendo com que essa informação não tenha nenhum tipo de cache, seja no CDN ou no navegador do usuário. Já endpoints que retornam dados comuns, como lista de estados e cidades, podem ter cache sem problemas.

Timeout do API Gateway

Por fim, o último item que quero apontar é referente ao API Gateway, e o timeout máximo de 30 segundos para cada execução. Esse timeout não pode ser aumentado. Devemos então saber que não podemos ter chamadas que levem mais de 30 segundos, ou a conexão com o usuário será interrompida pelo API Gateway. Em uma aplicação web normal, nunca é desejável que tenhamos execuções tão demoradas assim. Porém em determinados casos, por exemplo, uma geração de relatório, é possível que esse processo leve mais do que 30 segundos. Nesses casos, devemos executar esse processo de forma assíncrona. O Zappa nos ajuda nessa tarefa, pois ele possui o recurso de Asynchronous Task Execution. No caso de geração de relatórios, o que nós fazemos é disparar a geração deles assincronamente utilizando esse recurso do Zappa e finalizar a execução indicando ao usuário que o relatório está sendo gerado. A rotina assíncrona então gera o relatório e o salva no S3. Quando ele está pronto, disponibilizamos a URL do S3 para que o usuário faça download.

Utilizar essa arquitetura no desenvolvimento de projetos possui várias vantagens, dentre facilidade de deploy, facilidade de escalonamento e principalmente o custo. A forma de cobrança do AWS Lambda é pelo tempo de cada execução, isso faz com que nada seja cobrado caso nenhum usuário esteja utilizando a aplicação (por exemplo finais de semana ou durante a noite). Porém, como descrevi neste artigo, os itens listados precisam ter atenção extra e devem ser considerados ao escolher desenvolver uma aplicação serverless.

--

--