Escalando sob Demanda: Como o Serverless Lida com Picos de Tráfego com Facilidade

Escalando sob Demanda: Como o Serverless Lida com Picos de Tráfego com Facilidade

No artigo anterior, discutimos dois modelos de processo para Serverless: execução-até-conclusão e processos de longa duração. A diferença chave entre esses modelos está em se a instância da função termina imediatamente após a execução. Além disso, exploramos dois cenários: orquestração de dados e orquestração de serviços.

Você pode se perguntar se esses cenários podem ser implementados usando processos de longa duração. A resposta é sim, mas é importante notar que execução-até-conclusão é a forma mais pura de Serverless. Então, qual é a lógica subjacente?

Para compreender totalmente isso, precisamos introduzir um conceito crucial na evolução da arquitetura de aplicações complexas da internet: escalabilidade, que é o foco deste artigo.

Imagine que 200 usuários acessem simultaneamente a página inicial index.html da sua aplicação web desenvolvida localmente. O que acontece com sua instância de servidor web local?

Vamos descrever o estado do seu PC. Primeiramente, 200 conexões TCP/IP são estabelecidas entre os clientes e o seu PC, que mal consegue lidar com isso. Em seguida, todos os 200 clientes iniciam simultaneamente requisições HTTP “GET/”. O processo principal do seu servidor web cria “número de núcleos de CPU -1” processos filhos concorrentemente para lidar com essas requisições. Note que subtraímos um do número de núcleos para reservar um para o processo principal.

Por exemplo, uma CPU de 4 núcleos cria três processos filhos para lidar com três requisições de clientes simultaneamente, enquanto as requisições restantes ficam em fila. Os processos filhos começam a processar as requisições “GET/”, combinam as regras de roteamento, entram na função de controle correspondente e retornam index.html aos clientes. Assim que um processo filho envia o arquivo index.html, o processo principal o recicla e cria um novo para lidar com a próxima requisição até que todas sejam processadas.

Entendendo isso, a próxima pergunta se torna simples. Como melhoramos a velocidade de processamento da nossa fila de clientes?

Escalonamento Vertical vs. Escalonamento Horizontal

Uma solução óbvia é aumentar o número de núcleos de CPU. Podemos conseguir isso atualizando a configuração de uma única máquina, por exemplo, de 4 para 8 núcleos, resultando em 7 processos filhos concorrentes.

Além de aumentar diretamente os núcleos de CPU, podemos adicionar mais máquinas (cada uma com 4 núcleos). Ao distribuir 500 clientes para cada uma das duas máquinas, também podemos aumentar o número de processos filhos concorrentes para 6.

Aumentar ou diminuir o desempenho de uma única máquina é escalonamento vertical, que muitas vezes vem com uma curva de custo íngreme à medida que o desempenho aumenta. Portanto, é necessário considerar cuidadosamente ao adotar essa abordagem. Por outro lado, aumentar ou diminuir o número de máquinas é escalonamento horizontal, uma abordagem mais custo-efetiva e nosso método de escalonamento padrão.

Agora, vamos adicionar alguma complexidade. Enquanto index.html é um único arquivo, e os dados? Seja escalonando vertical ou horizontalmente, precisamos reiniciar as máquinas. No nosso exemplo de lista de tarefas, os dados são armazenados na memória e são redefinidos a cada reinicialização. Então, como preservamos nossos dados durante o escalonamento?

Stateful vs. Stateless

Os nós em uma topologia de rede podem ser categorizados como stateful ou stateless, dependendo se armazenam estado. Os nós stateful retêm estado, ou seja, armazenam dados. Portanto, exigem atenção extra, demandando estabilidade e resistência a mudanças frequentes. Por exemplo, bancos de dados tipicamente empregam uma estrutura mestre-escravo, permitindo a troca imediata para um nó escravo se o nó mestre encontrar problemas, garantindo a disponibilidade contínua do serviço.

Os nós stateless, por outro lado, não armazenam nenhum estado ou apenas mantêm dados temporários não confiáveis. Devido à falta de estado, os nós stateless podem ser escalados horizontalmente para lidar com alta concorrência e reduzidos a zero quando não há tráfego (parece familiar?). Os nós stateful, no entanto, não podem fazer isso. Em cenários com flutuações significativas de tráfego entre horários de pico e fora de pico, precisamos projetar nós stateful para lidar com o tráfego de pico, mantendo os custos operacionais mesmo durante períodos de baixo tráfego.

Um banco de dados é um nó stateful típico, pois armazena persistentemente as tarefas dos usuários. Da mesma forma, um balanceador de carga também é stateful, muito parecido com o processo principal mantendo a fila de clientes em nosso experimento mental. Ele precisa armazenar as conexões dos clientes para retornar os resultados processados pela nossa aplicação web de volta aos clientes.

Voltando aos nossos modelos de processo, a execução-até-conclusão é inerentemente stateless, pois termina após a execução, tornando impossível usá-la sozinha para armazenamento persistente de dados. Os processos de longa duração, no entanto, são naturalmente stateful porque seu processo principal não sai, permitindo armazenar alguns valores.

No entanto, no Serverless, mesmo que armazenemos valores no processo principal de um processo de longa duração, o provedor de nuvem pode ainda assim retomá-lo. Mesmo com instâncias reservadas, os dados na memória dos nós escalados permanecem isolados.

Portanto, para tornar os processos de longa duração stateless, precisamos evitar armazenar valores no processo principal ou apenas armazenar variáveis temporárias. Os dados persistentes devem ser movidos para nós stateful dedicados, como bancos de dados.

Ao separar o armazenamento de dados do nó do processo principal e garantir que o processo principal não retenha dados, nossa aplicação se torna stateless. Armazenamos os dados em um nó de banco de dados stateful separado. Este exemplo se transforma no cenário Serverless de longa duração discutido no artigo anterior, onde nos conectamos ao banco de dados durante a inicialização do processo principal e acessamos os dados através de processos filhos. No entanto, essa abordagem tem uma desvantagem significativa: aumenta diretamente o tempo de cold start. Existe uma solução melhor?

Vamos considerar uma abordagem alternativa para persistência de dados. Por que temos que nos conectar ao banco de dados nós mesmos? Nossas operações CRUD (create, read, update, delete) nos dados essencialmente envolvem processos filhos reutilizando a conexão TCP estabelecida pelo processo principal, enviando comandos de banco de dados e recuperando dados. Imagine se pudéssemos enviar instruções ao banco de dados usando requisições HTTP como POST, DELETE, PUT e GET. Isso não nos permitiria aproveitar os conceitos de orquestração de dados e serviços da lição anterior?

O que é BaaS

De fato, todo esse trabalho preparatório nos leva ao protagonista de hoje: BaaSificação. As operações de interface de dados POST, DELETE, PUT e GET correspondem aos métodos HTTP semânticos das APIs RESTful. Tomando MySQL como exemplo, POST mapeia para o comando CREATE, DELETE para DELETE, PUT para UPDATE e GET para SELECT. Essa correspondência semântica um-para-um nos permite traduzir naturalmente operações MySQL em operações de API RESTful.

As abordagens tradicionais de banco de dados, devido à reutilização de conexão TCP e baixa sobrecarga de comunicação, são mais rápidas para as mesmas operações em comparação com HTTP. Embora o Serverless possa se conectar diretamente a bancos de dados, conectar-se a bancos de dados tradicionais usando endereços IP muitas vezes se mostra desafiador em ambientes de nuvem com segmentação VPC. Portanto, para conexões de banco de dados Serverless, normalmente dependemos de serviços BaaS fornecidos pelos provedores de nuvem, embora muitos serviços BaaS ainda não estejam maduros.

Indo um passo além, se o Serverless não é adequado para nós stateful, por que não externalizar todas as operações stateful como interfaces de dados? Isso permite que nossas funções Serverless utilizem a abordagem de orquestração de dados discutida na lição anterior e alcancem escalonamento livre.

Resumo

A razão pela qual o modelo de execução-até-conclusão é considerado mais puro do que o modelo de processo de longa duração é porque este último pode ser enganoso, tentando-nos a tratá-lo como PaaS e usá-lo como um nó stateful para armazenamento permanente de dados. No entanto, no Serverless, mesmo com processos de longa duração, os provedores de nuvem ainda podem retomar nossas instâncias de função.

Assim como no nosso exemplo, onde armazenar dados na memória resultava em redefinições a cada reinicialização, ao adotar uma mentalidade de orquestração de dados e transformar operações de banco de dados backend em interfaces de dados, podemos transferir o armazenamento de dados no Serverless para aplicações backend e interagir com elas usando orquestração de dados, conforme explicado na lição anterior. No entanto, precisamos ir além da criação de interfaces de dados para aplicações backend. Precisamos abraçar a BaaSificação, liberando os engenheiros de backend das preocupações operacionais do lado do servidor durante o desenvolvimento.

Para escalonamento, podemos escolher entre escalonamento vertical e horizontal. O escalonamento vertical foca em melhorar o desempenho de uma única máquina, mas muitas vezes vem com um aumento de custo íngreme, tornando-o uma escolha cautelosa. O escalonamento horizontal envolve aumentar o número de máquinas, oferecendo uma curva de custo mais suave e servindo como nosso método de escalonamento padrão.

Os nós stateful armazenam dados, enquanto os nós stateless processam dados sem retê-los. Apenas nós stateless podem ser escalados livremente. Os nós stateful, responsáveis por armazenar dados críticos, exigem manuseio cuidadoso. Se quisermos que os nós da nossa topologia de rede escalem livremente, precisamos externalizar suas operações de dados para nós stateful dedicados.

Quando nossas funções Serverless acessam nós stateful, é preferível que esses nós forneçam interfaces de dados em vez de depender apenas de comandos de banco de dados, pois as conexões de banco de dados introduzem sobrecarga adicional para funções Serverless. Além disso, para simplificar o desenvolvimento para engenheiros de backend, devemos buscar a BaaSificação dos nós stateful. Iremos nos aprofundar mais na BaaSificação em artigos subsequentes.

Novita AI é a plataforma tudo-em-um na nuvem que impulsiona suas ambições de IA. APIs integradas, serverless, instância GPU — as ferramentas custo-efetivas que você precisa. Elimine a infraestrutura, comece gratuitamente e torne sua visão de IA realidade.

Leitura Recomendada

Desvendando a Revolução: Explorando o Mundo da Computação Serverless

Análise Serverless, Começando pelos Modelos de Dados