Escalabilidade — System Design
Escalabilidade é a capacidade de um sistema de continuar atendendo sua demanda conforme o número de usuários, requisições e volume de dados aumenta.
Imagine uma aplicação que começa com:
100 usuários
│
▼
┌─────────────┐
│ Server │
│ 2 CPU │
│ 4 GB RAM │
└─────────────┘
Com o crescimento da aplicação:
10.000 usuários
100.000 usuários
1.000.000 usuários
Um único servidor pode deixar de ser suficiente.
Nesse momento, precisamos pensar em como aumentar a capacidade do sistema.
Existem duas estratégias principais:
- Escalabilidade vertical
- Escalabilidade horizontal
Além disso, precisamos de componentes como Load Balancers, servidores Stateless e arquiteturas distribuídas.
1. O que você vai aprender
Neste artigo, vamos entender:
- Diferença entre escalabilidade vertical e horizontal
- Como funciona um Load Balancer
- Estratégias de distribuição de tráfego
- Round-Robin
- Least Connections
- IP Hash
- Por que servidores Stateless são importantes
- As 3 camadas de um sistema web
- Como escalar cada camada
- Quando é hora de escalar
- Quais métricas observar
- Trade-offs envolvidos nas decisões de escalabilidade
2. O que é escalabilidade?
Escalabilidade é a capacidade de um sistema aumentar sua capacidade de processamento conforme a demanda cresce.
Podemos representar:
Demanda
│
│ /
│ /
│ /
│ /
│ /
│ /
└────────────────── Tempo
À medida que a demanda aumenta, precisamos aumentar a capacidade:
Demanda ↑
│
│
▼
Capacidade ↑
O objetivo é evitar que o sistema fique:
- Lento
- Instável
- Indisponível
- Sobrecarregado
3. Escalabilidade Vertical
A escalabilidade vertical, também chamada de Scale Up, consiste em aumentar os recursos de um servidor existente.
Por exemplo:
ANTES
┌─────────────────────┐
│ SERVER │
├─────────────────────┤
│ 2 CPU │
│ 4 GB RAM │
│ 100 GB SSD │
└─────────────────────┘
Podemos aumentar para:
DEPOIS
┌─────────────────────┐
│ SERVER │
├─────────────────────┤
│ 16 CPU │
│ 64 GB RAM │
│ 1 TB SSD │
└─────────────────────┘
O sistema continua rodando em um único servidor.
CLIENTS
│
▼
┌─────────────┐
│ SERVER │
│ │
│ 16 CPU │
│ 64 GB RAM │
└─────────────┘
Vantagens
A principal vantagem é a simplicidade.
Não precisamos necessariamente modificar a arquitetura.
Podemos continuar utilizando:
Client
│
▼
Server
│
▼
Database
Outras vantagens:
- Fácil implementação
- Menor complexidade operacional
- Menos componentes distribuídos
- Mais simples de monitorar
- Menos problemas de sincronização
Desvantagens
Existe um limite físico.
Não podemos aumentar os recursos infinitamente.
Além disso, servidores maiores podem se tornar muito caros.
Também existe outro problema:
Se o único servidor falhar, toda a aplicação pode ficar indisponível.
Temos então um possível Single Point of Failure.
CLIENTS
│
▼
┌───────────┐
│ SERVER │
└─────┬─────┘
X
FALHA
│
▼
SISTEMA OFFLINE
4. Escalabilidade Horizontal
A escalabilidade horizontal, ou Scale Out, consiste em adicionar mais servidores.
Em vez de termos:
1 servidor gigante
podemos ter:
3 servidores menores
Por exemplo:
CLIENTS
│
▼
┌──────────┐
│ LOAD │
│ BALANCER │
└────┬─────┘
│
┌────────┼────────┐
│ │ │
▼ ▼ ▼
┌─────┐ ┌─────┐ ┌─────┐
│App 1│ │App 2│ │App 3│
└─────┘ └─────┘ └─────┘
O Load Balancer distribui as requisições entre os servidores.
Se a demanda aumentar:
3 servidores
│
▼
5 servidores
│
▼
10 servidores
Podemos adicionar novas instâncias.
5. Vertical vs Horizontal
CaracterísticaVerticalHorizontalEstratégiaAumentar servidorAdicionar servidoresComplexidadeMenorMaiorLimite físicoSimMuito maiorAlta disponibilidadeLimitadaMelhorCusto inicialPode ser menorPode ser maiorDistribuição de cargaNãoSimFalha de um servidorImpacto altoPode ser absorvidaOperaçãoSimplesMais complexa
De maneira geral:
Scale Up
SERVER
│
▼
SERVER MAIOR
Enquanto:
Scale Out
SERVER
SERVER
SERVER
SERVER
Em sistemas modernos de grande escala, a escalabilidade horizontal é frequentemente preferida.
Porém, isso não significa que devemos ignorar a vertical.
Uma combinação das duas estratégias é bastante comum.
6. Load Balancer
Quando temos vários servidores, precisamos de uma maneira de distribuir as requisições.
É aí que entra o Load Balancer.
Sua função básica é:
CLIENTS
│
▼
┌───────────────┐
│ LOAD BALANCER │
└───────┬───────┘
│
┌─────────┼─────────┐
│ │ │
▼ ▼ ▼
SERVER 1 SERVER 2 SERVER 3
O cliente não precisa saber qual servidor irá processar a requisição.
Ele envia:
GET /users
O Load Balancer decide:
Servidor 1
Na próxima requisição:
GET /products
Pode decidir:
Servidor 2
Isso permite distribuir a carga.
7. Round-Robin
Round-Robin é uma das estratégias mais simples.
As requisições são distribuídas sequencialmente.
Imagine:
SERVER 1
SERVER 2
SERVER 3
As requisições seriam:
Request 1 → Server 1
Request 2 → Server 2
Request 3 → Server 3
Request 4 → Server 1
Request 5 → Server 2
Request 6 → Server 3
Visualmente:
LOAD BALANCER
│
┌────────────┼────────────┐
▼ ▼ ▼
Server 1 Server 2 Server 3
▲ ▲ ▲
│ │ │
└────────────┴────────────┘
CICLO
Vantagem
É simples e funciona bem quando:
Server 1 = mesma capacidade
Server 2 = mesma capacidade
Server 3 = mesma capacidade
E quando as requisições possuem custos semelhantes.
Problema
Imagine:
Server 1 → 10 requisições pesadas
Server 2 → 10 requisições leves
Server 3 → 10 requisições leves
O Round-Robin pode considerar todos os servidores igualmente disponíveis.
Mas, na prática:
Server 1 → 95% CPU
Server 2 → 20% CPU
Server 3 → 15% CPU
O número de requisições não representa necessariamente a carga real.
8. Least Connections
O Least Connections tenta enviar a requisição para o servidor que possui menos conexões ativas.
Imagine:
Server 1 → 100 conexões
Server 2 → 20 conexões
Server 3 → 50 conexões
Uma nova requisição será enviada para:
Server 2
Visualmente:
LOAD BALANCER
│
│
▼
Menos conexões
│
▼
SERVER 2
Isso pode funcionar melhor quando as requisições possuem durações diferentes.
Por exemplo:
Request A → 10 segundos
Request B → 100 ms
O Round-Robin pode distribuir igualmente.
O Least Connections tenta considerar a quantidade de trabalho atualmente em andamento.
Limitação
Número de conexões não é necessariamente igual a consumo de CPU ou memória.
Podemos ter:
Server 1
10 conexões
90% CPU
Server 2
50 conexões
20% CPU
O algoritmo pode interpretar que o Server 1 está mais livre, mesmo que esteja quase saturado.
Por isso, sistemas mais sofisticados podem utilizar métricas de saúde e carga.
9. IP Hash
O IP Hash utiliza o endereço IP do cliente para determinar qual servidor irá atender a requisição.
Por exemplo:
Cliente A
IP: 192.168.1.10
│
▼
Server 1
Enquanto:
Cliente B
IP: 192.168.1.20
│
▼
Server 2
O objetivo é fazer com que o mesmo cliente seja direcionado consistentemente para o mesmo servidor.
Isso pode ser útil quando existe algum estado local no servidor.
Por exemplo:
Cliente A
│
▼
Server 1
│
▼
Session local
Nas próximas requisições:
Cliente A
│
▼
Server 1
Problema
Se o servidor cair:
Server 1
X
O cliente pode ser direcionado para outro servidor.
Nesse momento:
Server 2
│
└── Não possui a sessão local
Isso mostra um dos principais problemas de manter estado dentro do servidor.
10. Stateless vs Stateful
Para escalar horizontalmente de maneira eficiente, normalmente queremos servidores Stateless.
Um servidor Stateless não depende de informações armazenadas localmente entre requisições.
Por exemplo:
Request 1
│
▼
Server 1
Request 2:
Request 2
│
▼
Server 3
O Server 3 consegue processar a requisição sem precisar saber que o Server 1 processou a anterior.
11. O problema de servidores Stateful
Imagine uma aplicação que armazena a sessão na memória do servidor.
CLIENT
│
▼
SERVER 1
│
▼
Session: user123
Na próxima requisição:
CLIENT
│
▼
SERVER 2
O Server 2 não conhece:
Session: user123
Isso pode causar problemas.
Uma solução seria usar:
Sticky Sessions
Ou seja, sempre enviar o usuário para o mesmo servidor.
Porém, isso dificulta a escalabilidade.
12. Transformando o sistema em Stateless
Uma abordagem melhor é retirar o estado do servidor.
Podemos utilizar:
CLIENT
│
▼
┌──────────────┐
│ Load Balancer│
└───────┬──────┘
│
┌────────┼────────┐
▼ ▼ ▼
App 1 App 2 App 3
│ │ │
└────────┼────────┘
│
▼
Redis
Agora:
App 1 ──┐
App 2 ──┼──► Redis
App 3 ──┘
Todas as instâncias podem acessar o mesmo estado compartilhado.
Isso permite:
Request 1 → App 1
Request 2 → App 3
Request 3 → App 2
Sem problemas.
13. Stateless com JWT
Outra abordagem comum é utilizar tokens.
Por exemplo:
CLIENT
│
│ Login
▼
SERVER
│
▼
JWT
O cliente recebe:
JWT Token
E envia em cada requisição:
Authorization: Bearer <token>
Agora qualquer servidor pode validar o token:
REQUEST
│
▼
┌────────────┐
│ Load │
│ Balancer │
└─────┬──────┘
│
┌─────┼─────┐
▼ ▼ ▼
App 1 App 2 App 3
│ │ │
└─────┼─────┘
│
Valida JWT
Não é necessário armazenar a sessão na memória de uma instância específica.
Isso facilita o Scale Out.
14. As 3 camadas de um sistema web
Uma arquitetura web tradicional pode ser dividida em três camadas:
1. Presentation Layer
2. Application Layer
3. Data Layer
Ou:
CLIENT
│
▼
┌───────────────┐
│ PRESENTATION │
└───────┬───────┘
│
▼
┌───────────────┐
│ APPLICATION │
└───────┬───────┘
│
▼
┌───────────────┐
│ DATA │
└───────────────┘
Cada camada pode ter diferentes estratégias de escalabilidade.
15. Camada 1 — Presentation
Essa é a camada responsável pela interação com o usuário.
Pode incluir:
- Frontend
- HTML
- CSS
- JavaScript
- React
- Next.js
- Aplicações mobile
Uma estratégia comum é utilizar uma CDN.
CLIENT
│
▼
CDN
│
┌────────┴────────┐
│ │
▼ ▼
Cache Origin Server
Arquivos estáticos podem ser distribuídos globalmente:
JavaScript
CSS
Imagens
Vídeos
Fonts
Isso reduz a carga no servidor principal.
16. CDN
Imagine um usuário no Brasil acessando um servidor localizado nos Estados Unidos.
Sem CDN:
Brasil
│
│ Internet
│
└──────────────► EUA
│
Server
Com CDN:
Brasil
│
▼
CDN São Paulo
│
│ Cache Hit
▼
Resposta
Se o conteúdo já estiver armazenado na CDN, não precisamos acessar o servidor de origem.
Isso melhora:
- Latência
- Performance
- Disponibilidade
- Escalabilidade
17. Camada 2 — Application
É onde normalmente encontramos:
- APIs
- Backend
- Regras de negócio
- Autenticação
- Processamento
Podemos escalar horizontalmente:
CLIENT
│
▼
LOAD BALANCER
│
┌───────────┼───────────┐
▼ ▼ ▼
API 1 API 2 API 3
Essa camada é uma das mais fáceis de escalar horizontalmente quando os servidores são Stateless.
Se a demanda aumenta:
3 instances
│
▼
10 instances
Podemos adicionar mais servidores.
18. Camada 3 — Data
Essa camada pode conter:
- Bancos SQL
- Bancos NoSQL
- Cache
- Storage
Ela geralmente é a mais difícil de escalar.
Podemos utilizar:
Read Replicas
APPLICATION
│
▼
PRIMARY
DATABASE
│
┌───────┴───────┐
▼ ▼
REPLICA 1 REPLICA 2
As escritas vão para:
PRIMARY
As leituras podem ser distribuídas:
READ
│
├── Replica 1
│
└── Replica 2
Isso ajuda a escalar sistemas com muitas leituras.
Sharding
Outra estratégia é dividir os dados.
Imagine:
Users 1 - 1.000.000
Podemos dividir:
Shard 1
Users 1 - 250.000
Shard 2
Users 250.001 - 500.000
Shard 3
Users 500.001 - 750.000
Shard 4
Users 750.001 - 1.000.000
Isso distribui os dados entre diferentes servidores.
Porém, adiciona bastante complexidade.
19. Escalando as três camadas
Uma arquitetura mais completa poderia ser:
CLIENTS
│
▼
CDN
│
▼
LOAD BALANCER
│
┌──────────┼──────────┐
▼ ▼ ▼
API 1 API 2 API 3
│ │ │
└──────────┼──────────┘
│
┌────────┴────────┐
▼ ▼
Redis Database
│
┌────────┴────────┐
▼ ▼
Primary Replicas
Cada camada possui uma estratégia diferente.
Presentation
│
└── CDN
Application
│
└── Load Balancer + Horizontal Scaling
Data
│
├── Cache
├── Replicas
├── Partitioning
└── Sharding
20. Quando é hora de escalar?
Não devemos escalar apenas porque:
"A aplicação está crescendo."
Precisamos observar métricas.
Alguns sinais importantes:
CPU
Memória
Latência
Throughput
Erros
Requests por segundo
Conexões
I/O
Database Load
21. CPU
Imagine:
CPU
20%
30%
40%
50%
60%
70%
80%
90%
95%
Se a CPU permanece constantemente em:
90%+
podemos ter um problema.
Isso pode indicar:
- Falta de capacidade
- Código ineficiente
- Query pesada
- Loop excessivo
- Processamento síncrono
Antes de simplesmente adicionar servidores, devemos investigar a causa.
22. Memória
Se a memória está constantemente alta:
RAM
95%
97%
99%
podemos ter:
- Memory leak
- Cache muito grande
- Objetos mantidos em memória
- Falta de recursos
Adicionar servidores pode ajudar, mas não necessariamente resolve a causa.
23. Latência
Uma das métricas mais importantes é o tempo de resposta.
Por exemplo:
P50 = 100 ms
P95 = 500 ms
P99 = 2 segundos
O P99 significa que 99% das requisições são mais rápidas que aquele valor.
Observar apenas a média pode esconder problemas.
Exemplo:
99 requisições → 100 ms
1 requisição → 10 segundos
A média pode parecer aceitável, mas existe um usuário sofrendo com uma latência muito alta.
Por isso, métricas como:
P50
P95
P99
são extremamente importantes.
24. Throughput
Throughput representa a quantidade de trabalho processada pelo sistema.
Por exemplo:
Requests per Second (RPS)
Imagine:
Servidor suporta:
1.000 RPS
A demanda atual:
900 RPS
Estamos próximos do limite.
Se a demanda crescer:
1.500 RPS
o sistema pode começar a apresentar:
- Aumento de latência
- Timeouts
- Erros
- Fila de requisições
Esse é um sinal de que precisamos aumentar a capacidade.
25. Error Rate
Devemos monitorar a quantidade de erros.
Por exemplo:
HTTP 500
HTTP 502
HTTP 503
Timeouts
Imagine:
Error Rate
0.1%
0.2%
0.3%
0.5%
1%
5%
Um crescimento repentino pode indicar:
- Saturação
- Falha de servidor
- Problema no banco
- Problema de rede
- Dependência externa indisponível
26. Database Load
Muitas vezes o problema não está na aplicação.
Podemos ter:
API
│
▼
Database
│
▼
CPU 100%
Nesse caso, adicionar mais servidores de aplicação:
API 1
API 2
API 3
API 4
API 5
não necessariamente resolve.
Na verdade, pode piorar.
Agora temos:
API 1 ──┐
API 2 ──┤
API 3 ──┼──► DATABASE
API 4 ──┤ │
API 5 ──┘ ▼
100% CPU
O gargalo está no banco.
Precisamos investigar:
- Queries lentas
- Índices
- Locks
- Conexões
- Cache
- Read replicas
- Particionamento
27. Gargalos
Um conceito fundamental é identificar o bottleneck.
Imagine:
Client
│
▼
API
│
▼
Database
Se:
API → 20% CPU
Database → 100% CPU
O banco é o gargalo.
Escalar a API não resolve.
Outro exemplo:
API → 100% CPU
Database → 20% CPU
Nesse caso, podemos escalar a camada de aplicação.
A regra é:
Escale o gargalo, não simplesmente o componente mais fácil de escalar.
28. Trade-offs da Escalabilidade
Escalar traz benefícios, mas também aumenta a complexidade.
Quanto mais distribuído o sistema:
┌────────┐
│ Server │
└────────┘
para:
Server 1
Server 2
Server 3
Server 4
Server 5
Database
Redis
Queue
CDN
Load Balancer
maior a complexidade operacional.
Agora precisamos lidar com:
- Rede
- Latência
- Falhas parciais
- Consistência
- Observabilidade
- Deploy
- Monitoramento
- Coordenação
29. Escalabilidade vs Custo
Mais servidores significam mais custos.
Por exemplo:
1 servidor
R$ 500/mês
Pode virar:
10 servidores
R$ 5.000/mês
Além disso:
Load Balancer
Redis
Database Replicas
CDN
Observabilidade
também possuem custos.
Por isso, devemos evitar escalar prematuramente.
30. Escalar verticalmente ou horizontalmente?
Uma estratégia prática:
Sistema pequeno
│
▼
Scale Up
│
▼
Monitoramento
│
▼
Gargalo identificado
│
▼
Scale Out
│
▼
Load Balancer
│
▼
Stateless
│
▼
Escalar componentes individualmente
Podemos começar simples:
Client
│
▼
Backend
│
▼
Database
Depois evoluir:
Client
│
▼
Load Balancer
│
├── Backend 1
├── Backend 2
└── Backend 3
│
▼
Redis
│
▼
Database
E somente quando necessário:
CDN
Load Balancer
API Servers
Cache
Queues
Workers
Read Replicas
Sharding
31. Resumo dos principais conceitos
Escalabilidade Vertical
Aumenta os recursos de uma máquina.
2 CPU
↓
16 CPU
Mais simples, mas possui limites.
Escalabilidade Horizontal
Adiciona mais máquinas.
Server 1
Server 2
Server 3
Mais complexa, mas oferece maior capacidade de crescimento.
Load Balancer
Distribui requisições.
Client
│
▼
Load Balancer
│
├── Server 1
├── Server 2
└── Server 3
Algoritmos comuns:
Round-Robin
Least Connections
IP Hash
Stateless
Os servidores não dependem de estado local.
Isso permite:
Request 1 → Server 1
Request 2 → Server 3
Request 3 → Server 2
Facilitando o Scale Out.
Três camadas
Presentation
│
└── CDN
Application
│
└── Load Balancer + Servers
Data
│
└── Cache + Replicas + Sharding
Métricas importantes
CPU
Memory
Latency
P95 / P99
RPS
Throughput
Error Rate
Database Load
Connections
I/O
32. Checklist para System Design
Quando estiver desenhando um sistema, pergunte:
Aplicação
- Os servidores são Stateless?
- Posso adicionar novas instâncias?
- Existe um Load Balancer?
- Qual algoritmo de distribuição usar?
Performance
- Qual é o RPS esperado?
- Qual é a latência aceitável?
- Qual é o P95?
- Qual é o P99?
Banco
- O banco é o gargalo?
- Preciso de Read Replicas?
- Preciso de cache?
- Preciso particionar os dados?
- Sharding é realmente necessário?
Disponibilidade
- O que acontece se um servidor cair?
- Existe Single Point of Failure?
- Existe redundância?
Escalabilidade
- O sistema escala verticalmente?
- O sistema escala horizontalmente?
- Qual componente será o primeiro gargalo?
Custo
- Quanto custa adicionar capacidade?
- Estou escalando antes de precisar?
- Existe uma solução mais simples?
Conclusão
Escalabilidade não significa simplesmente adicionar mais servidores.
Um sistema escalável é aquele que consegue aumentar sua capacidade de acordo com a demanda sem que a complexidade e o custo cresçam de maneira descontrolada.
A arquitetura geralmente evolui de:
Servidor único
para:
Load Balancer
│
├── Server 1
├── Server 2
└── Server 3
E posteriormente pode incorporar:
CDN
Load Balancer
Stateless APIs
Redis
Queues
Workers
Database Replicas
Partitioning
Sharding
A principal habilidade em System Design é saber quando e por que introduzir cada componente.
A regra mais importante é:
Não escale por hábito. Escale quando os requisitos e as métricas mostrarem que você precisa.
Antes de adicionar complexidade, procure o gargalo.
Antes de usar uma tecnologia, entenda o problema.
Antes de fazer sharding, veja se um bom índice resolve.
Antes de adicionar dezenas de servidores, descubra se uma query mal otimizada está consumindo todo o banco.
E, principalmente:
Uma boa arquitetura não é a que possui mais componentes. É a que resolve o problema com a complexidade necessária.