DEV Community

Laura Fumagalli
Laura Fumagalli

Posted on • Edited on

Escalabilidade DE ZERO A MILHÕES DE USUÁRIOS

Esse é a postagem 2 de uma série de postagens sobre arquitetura de sistemas.

Projetar um sistema que ofereça suporte a milhões de usuários é desafiador e uma jornada que exige refinamento contínuo e melhorias contínuas. Neste artigo, construímos um sistema que oferece suporte a um único usuário e o escalamos gradualmente para atender a milhões de usuários. Após essa leitura, você dominará algumas técnicas que vão ajudar a decifrar as perguntas sobre design de sistemas que sempre surgem.

Assim como uma maratona começa com um único passo, a construção de um sistema complexo também começa com etapas simples. Nas imagens a seguir, você encontrará uma série de representações de arquiteturas de sistemas que ilustram essa evolução. Para facilitar o entendimento inicial, começaremos com uma abordagem básica, na qual todo o sistema é executado em um único servidor.

Figura 1-1

A imagem acima (Figura 1-1) mostra a ilustração de uma configuração de servidor único onde tudo está sendo executado em um servidor: aplicativo web, banco de dados, cache, etc.

Figura 1-2

A imagem acima (Figura 1-2) é a mesma coisa, porém numerado com a ordem que as coisas acontecem. Para entender essa configuração, é útil investigar o fluxo de solicitações e a origem do tráfego. Vamos analisar primeiro o fluxo de solicitações:

  1. As pessoas usuárias acessam sites por meio de nomes de domínio, como api.mysite.com. Normalmente, o Sistema de Nomes de Domínio (DNS) é um serviço pago fornecido por terceiros e não hospedado por nossos servidores.

  2. O endereço de Protocolo de Internet (IP) é retornado ao navegador ou aplicativo móvel. No exemplo, o endereço IP 15.125.23.214 é retornado.

  3. Assim que o endereço IP for obtido, as solicitações do Protocolo de Transferência de Hipertexto (HTTP) são enviadas diretamente ao seu servidor web.

  4. O servidor web retorna páginas HTML ou respostas JSON para renderização.

Em seguida, vamos examinar a origem do tráfego. O tráfego para o seu servidor web vem de duas fontes: aplicativo web e aplicativo móvel.

  • Aplicativo web: ele usa uma combinação de linguagens do lado do servidor (Java, Python, etc.) para lidar com lógica de negócios, armazenamento, etc., e linguagens do lado do cliente (HTML e JavaScript) para apresentação.

  • Aplicativo móvel: o protocolo HTTP é o protocolo de comunicação entre o aplicativo móvel e o servidor web. JavaScript Object Notation (JSON) é o formato de resposta de API normalmente usado para transferir de dadosdevido à sua simplicidade. Um exemplo de resposta da API em formato JSON é mostrado abaixo:

GET /users/12 - Recupera o objeto da pessoa usuária cujo id = 12

Figura 1.3

Exemplo de retorno:

{
    "id": 12,
    "firstName": "Laura",
    "lastName": "Fumagalli",
    "address": {
        "streetAddress": "rua XPTO, 1800",
        "city": "São Paulo",
        "state": "Brazil",
        "postalCode": "12345-678"
    },
    "phoneNumbers": [
        "1191234-5678",
        "1994321-8765"
    ]
}
Enter fullscreen mode Exit fullscreen mode

Banco de dados

Com o crescimento da base de usuários, um servidor não é suficiente e precisamos de vários servidores: um para o tráfego web/móvel e outro para o banco de dados (Figura 1-3). Separar os servidores de tráfego web/móvel (camada web) e de banco de dados (camada de dados) permite que eles sejam dimensionados de forma independente.

Quais bancos de dados usar?

Você pode escolher entre um banco de dados relacional tradicional e um banco de dados não relacional. Vamos examinar suas diferenças.

Bancos de dados relacionais também são chamados de sistemas de gerenciamento de banco de dados relacional (RDBMS) ou banco de dados SQL. Os mais populares são MySQL, banco de dados Oracle, PostgreSQL, etc.

Bancos de dados relacionais representam e armazenam dados em tabelas e linhas. Você pode realizar operações de junção usando SQL em diferentes tabelas de banco de dados.

Bancos de dados não relacionais também são chamados de bancos de dados NoSQL. Os mais populares são CouchDB, Neo4j, Cassandra, HBase, Amazon DynamoDB, etc. Esses bancos de dados são agrupados em quatro categorias: armazenamentos de chave-valor, armazenamentos de gráficos, armazenamentos de colunas e armazenamentos de documentos. Operações de junção geralmente não são suportadas em bancos de dados não relacionais

Para a maioria dos desenvolvedores, bancos de dados relacionais são a melhor opção, pois existem há mais de 40 anos e, historicamente, têm funcionado bem. No entanto, se os bancos de dados relacionais não forem adequados para seus casos de uso específicos, é fundamental explorar além dos bancos de dados relacionais. Bancos de dados não relacionais podem ser a escolha certa se:

  • Seu aplicativo requer latência superbaixa.
  • Seus dados não são estruturados ou você não possui dados que se relacionam de algum forma (relacionais).
  • Você só precisa serializar e desserializar os dados (JSON, XML, YAML, etc.).
  • Você precisa armazenar uma grande quantidade de dados.

Escalabilidade vertical vs. escalabilidade horizontal

A escalabilidade vertical, conhecida como "scale-up", significa o processo de adicionar mais potência (CPU, RAM, etc.) aos seus servidores. A escalabilidade horizontal, conhecida como "scale-out", permite que você escale adicionando mais servidores ao seu pool de recursos.

Quando o tráfego é baixo, a escala vertical é uma ótima opção, e a simplicidade dessa escala vertical é sua principal vantagem. Infelizmente, ela vem com sérias limitações.

A escalabilidade vertical tem um limite rígido. É impossível adicionar CPU e memória ilimitadas em um único servidor.

A escalabilidade vertical não possui failover e redundância. Se um servidor cair, o site/aplicativo cai completamente com ele.

A escalabilidade horizontal é mais desejável para aplicações de grande escala devido às limitações do escalonamento vertical.

Na arquitetura anterior, os usuários eram conectados diretamente ao servidor web. Os usuários não conseguiriam acessar o site se o servidor web estivesse offline. Em outro cenário, se muitos usuários acessassem o servidor web simultaneamente e ele atingisse o limite de carga do servidor web, os usuários geralmente experimentariam uma resposta mais lenta ou não conseguiriam se conectar ao servidor. Um balanceador de carga(Load Balancer) é a melhor técnica para resolver esses problemas.

Load Balancer

Um balanceador de carga distribui uniformemente o tráfego de entrada entre servidores web definidos em um conjunto de balanceamento de carga. A imagem a seguir (Figura 1-4) mostra como um balanceador de carga funciona.

Figura 1-4

Conforme mostrado na imagem acima (Figura 1-4), os usuários se conectam diretamente ao IP público do balanceador de carga. Com essa configuração, os servidores web não podem mais ser acessados diretamente pelos clientes. Para aumentar a segurança, são utilizados IPs privados para a comunicação entre os servidores. Um IP privado é um endereço IP acessível apenas dentro da rede local, ou seja, ele não é alcançável a partir da internet. Dessa forma, o balanceador de carga se comunica com os servidores web utilizando esses IPs privados.

Após a adição de um balanceador de carga e de um segundo servidor web, conseguimos resolver com sucesso o problema de failover e melhorar a disponibilidade da camada web. Abaixo estão os detalhes dessa implementação:

  • Se o Servidor 1 ficar offline, todo o tráfego será automaticamente redirecionado para o Servidor 2 , garantindo que o site permaneça acessível.

  • Isso evita que o sistema fique fora do ar. Além disso, podemos adicionar um novo servidor web saudável ao pool de servidores, mantendo o balanceamento de carga e a alta disponibilidade.

  • Caso o tráfego do site aumente rapidamente e dois servidores não sejam suficientes, o balanceador de carga também consegue lidar com esse cenário. Basta adicionar mais servidores ao pool, e o balanceador começará automaticamente a distribuir as requisições entre todos eles.

Agora que a camada web está mais robusta e resiliente, surge uma nova pergunta: e a camada de dados?

Atualmente, o design utiliza apenas um único banco de dados, o que significa que não há suporte para failover nem redundância nessa camada. Isso representa um ponto único de falha e pode colocar a disponibilidade do sistema em risco.

Uma solução comum e eficaz para esse problema é a replicação de banco de dados , uma técnica amplamente utilizada para garantir redundância, alta disponibilidade e tolerância a falhas. Vamos explorar como implementá-la a seguir.

Figura 1-5

Replicação de banco de dados

Citado da Wikipédia: "A replicação de banco de dados pode ser usada em muitos sistemas de gerenciamento de banco de dados, geralmente com um relacionamento primário/secundário entre o original (primário) e as cópias (secundárias)" [3].

Um banco de dados primário geralmente é responsável pelas operações de escrita. Já os bancos de dados secundários recebem cópias dos dados do primário e são utilizados apenas para operações de leitura. Todos os comandos que modificam os dados, como INSERT, DELETE ou UPDATE, devem ser enviados ao banco de dados primário. A maioria das aplicações realiza muito mais operações de leitura do que de escrita, por isso o número de bancos de dados secundários em um sistema costuma ser maior do que o de primários. A imagem anterior (Figura 1-5) ilustra essa arquitetura com um banco de dados primário e vários secundários.

Vantagens da replicação de banco de dados:

✅ Melhor desempenho

No modelo primário/secundário, todas as gravações e atualizações ocorrem no nó primário, enquanto as operações de leitura são distribuídas entre os nós secundários. Essa divisão de responsabilidades melhora o desempenho do sistema, pois permite que um maior número de consultas seja processado em paralelo.

✅ Confiabilidade e tolerância a falhas

Se um servidor de banco de dados for afetado por um desastre natural, como um tufão ou terremoto, os dados ainda estarão disponíveis nos outros nós secundários. Isso reduz drasticamente o risco de perda de dados e aumenta a disponibilidade do sistema, mesmo em cenários adversos.

✅ Alta disponibilidade:

Ao replicar dados em diferentes locais, seu site permanece em operação mesmo se um banco de dados estiver offline, pois você pode acessar dados armazenados em outro servidor de banco de dados.

Discutimos anteriormente como o uso de um balanceador de carga contribui significativamente para a melhoria da disponibilidade da camada web. Agora, aplicamos a mesma pergunta à camada de dados: e se um dos bancos de dados ficar offline?

A arquitetura apresentada na imagem acima (Figura 1-5) é capaz de lidar com falhas nos bancos de dados de forma eficiente. Abaixo estão os cenários possíveis:

  • Se apenas um banco de dados secundário estiver disponível e ele ficar offline , as operações de leitura serão temporariamente direcionadas ao banco de dados principal até que o problema seja resolvido. Assim que a falha for detectada, um novo banco de dados secundário será provisionado para substituir o antigo e retomar a redundância. Caso múltiplos bancos de dados secundários estejam disponíveis, as consultas de leitura são automaticamente redirecionadas para os nós ainda saudáveis. Paralelamente, um novo nó secundário é criado para substituir o que falhou, mantendo o nível desejado de disponibilidade e balanceamento de carga.

  • Se o banco de dados principal ficar indisponível , um dos bancos de dados secundários será promovido a novo banco de dados principal . Durante esse processo, todas as operações (incluindo escritas) são temporariamente executadas no novo nó principal. Em seguida, um novo banco de dados secundário é adicionado para retomar o processo de replicação.

⚠️ Importante: Em ambientes de produção, a promoção automática de um novo banco de dados principal pode ser um processo complexo. Isso ocorre porque, dependendo do tipo de replicação utilizada (síncrona ou assíncrona), alguns dados podem não estar totalmente atualizados nos bancos de dados secundários. Nesses casos, é necessário realizar uma etapa de recuperação de dados para garantir a consistência antes da promoção oficial do novo nó principal.

Embora existam outros métodos de replicação, como replicação circular por exemplo, que podem trazer vantagens adicionais em certos cenários, essas configurações tendem a ser mais complexas e fogem ao escopo deste artigo. Pessoas leitoras interessadas podem pesquisar sobre para aprofundar seus conhecimentos sobre esses tópicos, é um estudo bem interessante.

A imagem abaixo (Figura 1-6) ilustra o projeto atualizado do sistema após a inclusão do balanceador de carga e da replicação do banco de dados. Essa arquitetura reflete uma evolução importante na disponibilidade e escalabilidade do sistema, garantindo maior resiliência frente a falhas e aumento de tráfego.

Figura 1.6

Excelente! Agora vamos dar uma olhada no design atual do sistema:

  • Um usuário consulta o DNS e obtém o endereço IP público do balanceador de carga.
  • Em seguida, o usuário estabelece a conexão com o balanceador de carga usando esse endereço IP.
  • A solicitação HTTP é então roteada para um dos servidores web disponíveis (Servidor 1 ou Servidor 2), conforme a política de balanceamento configurada.
  • Quando uma requisição envolve leitura de dados, o servidor web recupera as informações de um banco de dados secundário.
  • Já as operações que modificam os dados, como inserções, atualizações ou exclusões, são sempre direcionadas ao banco de dados primário, garantindo consistência nos dados.

Agora que você tem uma compreensão sólida das camadas web e de dados, vamos dar o próximo passo: melhorar o tempo de resposta e carregamento do sistema.

No próximo artigo, vou explorar como isso pode ser alcançado com a adição de uma camada de cache e com a utilização de uma Rede de Distribuição de Conteúdo (CDN) para armazenar e entregar conteúdo estático, como arquivos JavaScript, CSS, imagens e vídeos.

Continue acompanhando para descobrir como otimizar ainda mais o desempenho da sua aplicação.

Top comments (0)