August 5, 2024

Escalabilidade: aplicações altamente escaláveis na nuvem

Hoje mais do que nunca é necessário e possível criar arquiteturas para sustentar aplicações altamente escaláveis na nuvem, preparadas para aumento significativo de demandas e alta escala.

Escalabilidade em software diz respeito a aumentar a infra-estrutura para suportar um número maior de requisições a uma aplicação.

Quem nunca ouviu falar dos grandes casos de sucesso que se transformaram em fracassos porque as aplicações não suportaram a demanda de usuários?

Já ouviu alguma história de empresa que fez campanha de marketing multi-milionária, mas ficou poucas horas no ar porque os servidores não suportaram o volume de usuários tentando acessar o site?

Essas coisas podem acontecer… Mas, hoje em dia não precisam mais acontecer!

Um dos grandes símbolos desse problema de escalar, foi a famosa baleia do Twitter que sempre aparecia quando o Twitter tinha problemas para escalar, ou seja, para suportar a crescendo demanda de usuários tentando acessar a plataforma.

A boa notícia é que nos tempos atuais está cada vez mais fácil criar aplicações do zero com capacidade de alta escalabilidade.

Migrar aplicações para a nuvem, mesmo sem alterações significativas (abordagem conhecida como lift and shift), fornece às organizações os benefícios de um serviço seguro e econômico a infraestrutura.

No entanto, para aproveitar ao máximo a elasticidade e agilidade que a nuvem oferece será preciso evoluir a arquitetura de suas aplicações para usufruir dos recursos da AWS.

Para conhecer as melhores práticas de arquitetura em nuvem leia o whitepaper Arquitetura para a nuvem: melhores práticas da AWS.

Nesse paper a AWS pontua algumas características muito importantes de aplicações devem ser altamente escaláveis dos quais vamos abordar agora.

Elasticidade

Sua infraestrutura de nuvem deve aumentar ou diminuir a escalabilidade vertical automaticamente, adicionando ou removendo capacidade de acordo com as políticas e métricas previamente definidas. Essa capacidade de aumentar e reduzir conforme a demanda é chamada de Elasticidade.

Uma estrutura elástica ajuda você a atender às demandas de suas aplicações e pagar apenas pelo que precisa e usa.

Dessa forma se tiver uma aplicação que precisa suportar muitos usuários ao longo do dia, mas poucos usuários de madrugada você pode ter mais servidores durante o dia e menos durante a madrugada, evitando assim gastar com recursos ociosos desnecessariamente.

Da mesma forma se o seu negócio tem uma alta demanda aos finais de semana e baixa demanda nos dias úteis, você também poderá ajustar a infra-estrutura e os investimentos de acordo essa necessidade.

Escalabilidade Horizontal e Vertical

É possível escalar aplicações de duas formas diferentes Horizontal e Vertical.

Na escalabilidade vertical você aumentar a capacidade do servidor (memória, storage, cpu, etc.).

Então, por exemplo, se sua aplicação está rodando num servidor com 64GB de memória e você aumentar para um servidor com 128GB está escalando verticalmente.

Já na escalabilidade horizontal em vez de aumentar a capacidade de um servidor, adicionam-se mais servidores e distribuem-se as requisições dentre esses servidores (geralmente através de um balanceador de cargas ou load balancer).

Graças a modelos de escalabilidade horizontal que é possível ter servidores com nível de escala tão grande como Google, Facebook e tantos outros que fazem parte de nosso dia-a-dia atualmente.

Balanceamento de Carga

Como o nome surge os balanceadores de carga ou load balancers, são responsáveis por distribuir a carga de uma aplicação entre diferentes servidores num cenário de escalabilidade horizontal.

A AWS oferece um serviço chamado Elastic Load Balancing que distribui automaticamente o tráfego de entrada de aplicativos entre diversos destinos, como instâncias do Amazon EC2, contêineres, endereços IP e funções Lambda.

O serviço pode lidar com uma carga variável de tráfego dos aplicativos em uma única zona de disponibilidade ou em diversas zonas de disponibilidade.

Escalabilidade e o estado da aplicação

Um dos elementos importantes em se tratando de escalabilidade é a maneira que você lida com o estado das sessões de suas aplicações.

Em aplicações web é muito comum cada sessão de usuário receba um id que fica armazenado em um cookie no client do visitante e então do lado do servidor (server side) é possível armazenar e persistir um estado da interação deste usuário.

Como exemplo disso, imagine numa experiência de e-commerce, o carrinho de compras.

Quando um usuário adiciona itens no carrinho essa informação precisa ser armazenada em algum lugar, tradicionalmente, isso fica armazenado da sessão do usuário que fica na memória do servidor de aplicação.

Isso se torna um problema se você quer escalar sua aplicação horizontalmente porque nesse cenário, as diferentes requisições de um mesmo usuário podem ser roteadas para diferentes servidores e então a informação persistida na memória (os itens adicionados no carrinho) estariam na memória de apenas um desses servidores e não poderá ser encontrada nos outros servidores.

Já um aplicativo sem estado é um aplicativo que não precisa de conhecimento das interações anteriores e não armazena informações da sessão. Por exemplo, um aplicativo que, considerando a mesma entrada, fornece a mesma resposta para qualquer usuário final, é um aplicativo sem estado.

Os aplicativos sem estado podem ser dimensionados horizontalmente porque qualquer um dos recursos de computação disponíveis (servidores, containers, etc.) pode atender a qualquer solicitação. Sem sessão armazenada, você pode simplesmente adicionar mais recursos de computação, conforme necessário.

Nos casos em que você precisar armazenar estado e também precisar lidar com escalabilidade horizontal, recomenda-se que você utilize um banco de dados externo em memória como Redis ou o Memcached. Outra saída é usar um banco de dados como DynamoDB que tem alta disponibilidade, durabilidade e escala automaticamente.

Sticky Session

Nos casos em que você já possui uma aplicação que armazena estado nos servidores e precisa escalar horizontalmente mesmo assim também há uma saída conhecida como Sticky Session.

Na estratégia Sticky Session ou Session Affinity o load balancer sempre vai rotear o mesmo usuário para o mesmo servidor de forma a assegurar que os dados (estado) sejam encontrados na memória daquele servidor específico.

Apesar de funcionar, o balanceamento de carga pode ser menos efetivo com Sticky Session porque mesmo que um servidor esteja mais sobrecarregado do que outros, o load balancer vai ter que seguir enviando as requests dos mesmos usuários para os mesmos servidores.

Outro problema dessa abordagem é que se uma das máquinas falhar a sessão do usuário será perdida e consequente seus dados (como os itens do carrinho de compra, por exemplo).

Estado e Banco de Dados

Além dos dados da sessão de usuário, a grande maioria das aplicações vai ter que persistir o estado em algum tipo de banco de dados, para que os dados possam ser recuperados em operações subsequentes ou em análises de dados.

Então é muito importante garantir que o banco de dados também vai suportar o mesmo nível de escala que a sua aplicação precisa.

Não adianta o backend de sua aplicação ser altamente escalável com escalabilidade horizontal se o banco de dados de sua escolha não suportar a carga.

Seguindo nosso exemplo de e-commerce, depois que o usuário fechar o carrinho de compras, será necessário gerar um pedido para que seja processado e os itens do carrinho sejam efetivamente separados, faturados e enviados para o consumidor.

Para isso será necessário utilizar um banco de dados, tradicionamente, utilizam-se os banco de dados relacionais devido a sua facilidade se serem utilizados em aplicações transacionais tanto para armazenar os dados como também gerar relatórios e fazer consultas ad-hoc posteriormente.

Para muitos casos um banco de dados relacional pode ser suficiente, e atualmente com soluções como o Amazon Aurora que torna possível escalar bancos relacionais facilmente com réplicas de leitura e escrita (multi-master).

Para casos de extrema necessidade de escala recomenda-se a utilização de bancos de dados não relacionais como DynamoDB que tem capacidade de escalar muito além do que os bancos de dados relacionais inclusive mais do que o Aurora.

Nesses casos naturalmente para fazer análise de dados (Analytics/OLAP) vai ser necessário extrair os dados para outro tipo de banco de dados como o Redshift, por exemplo, que é um banco de dados adequado mais adequado para Analytics e consultas ad-hoc.

Em banco de dados como o DynamoDB apesar de se ter capacidade de escala que tende a infinito as consultas devem ser bem planejadas e as possibilidades de consultas limitadas aos casos de uso mais essenciais.

Distribuição de carga empurrada e puxada

Em se tratando de escalabilidade horizontal há dois tipos de distribuição de carga que podem ser adotados.

O mais comum é utilizando um Load Balancer como falamos mais acima, porém, essa não é a única maneira.

Na distribuição de carga empurrada para criar aplicações altamente escaláveis, como o nome sugere, você recebe a carga e empurra para outro servidor, isso pode ser feito por um load balancer ou por servidor DNS, como o Route 53.

No modelo de distribuição de carga puxada que é muito comum nas arquiteturas reativas, para criar aplicações altamente escaláveis você pode simplesmente incluir o que precisa ser processado numa fila ou stream (SQS, Kineses ou Kafka, por exemplo) para que seja puxada e processada no momento oportuno.

Conclusão

Atualmente está cada vez mais fácil de ser criar aplicações altamente escaláveis graças a novas tecnologias e provedores de nuvem como a AWS.

Nesse artigo apresentei os conceitos de escalabilidade horizontal, escalabilidade vertical, distribuição de carga puxada, distribuição de carga empurrada, Session Sticky e Load Balancers.

Espero que esses conceitos te ajudem a criar aplicações melhores e mais escaláveis.

Se ficar alguma dúvida, deixe sua pergunta nos comentários.