Arquitetura de Software • Nathan Geeksman • 08/04/2026

Escalabilidade Horizontal: Crescer sem Quebrar

As aplicações de software continuam a ser cada vez mais complexas e escaláveis, exigindo soluções eficazes para lidar com as crescentes demandas de usuários. A escalabilidade horizontal é uma abordagem comprovadamente eficaz para atender às necessidades das aplicações que precisam se adaptar rapidamente às mudanças nos volumes de tráfego, dados e usuários. Neste contexto, a escolha da estratégia certa de escalabilidade horizontal pode ser desafiadora, pois envolve considerações técnicas, econômicas e organizacionais.

Existe um momento previsível na vida de qualquer aplicação de sucesso: o servidor que sempre deu conta começa a engasgar. CPU constantemente alta, tempo de resposta subindo, alertas aparecendo nos horários de pico. A solução óbvia — colocar uma máquina maior — funciona, mas tem um teto. Em algum ponto, não existe máquina grande o suficiente, ou o custo dela deixa de fazer sentido.

Escalabilidade horizontal é a alternativa: em vez de um servidor mais poderoso, você coloca mais servidores. A carga se divide, o sistema cresce adicionando peças, e a capacidade teórica de expansão passa a ser praticamente ilimitada. É a abordagem que permite que uma aplicação passe de mil para um milhão de usuários sem uma reescrita completa da infraestrutura.

Mas escalar horizontalmente não é só adicionar máquinas. É uma decisão de arquitetura com implicações em como o estado é gerenciado, como as sessões funcionam, como os dados são compartilhados — e ignorar isso transforma a solução no problema.

Vertical vs. horizontal: a diferença que importa

Escala vertical (scale up) significa dar mais recursos ao servidor existente: mais RAM, mais CPU, disco mais rápido. É simples de executar e não exige mudança na aplicação, mas tem limites físicos e econômicos claros. Acima de um certo ponto, dobrar a capacidade custa mais que o dobro.

Escala horizontal (scale out) adiciona instâncias ao invés de engrandecer uma só. A aplicação precisa estar preparada para isso — múltiplas instâncias rodando em paralelo, compartilhando carga, com algum mecanismo decidindo quem responde o quê. O investimento inicial é maior, mas o modelo escala de forma muito mais linear: dobrar a capacidade fica mais próximo de dobrar o custo.

As duas abordagens não são mutuamente exclusivas. Na prática, a maioria dos sistemas usa alguma combinação: instâncias horizontalmente escaladas, cada uma com recursos verticais adequados ao workload.

O que precisa mudar na aplicação

Adicionar servidores sem preparar a aplicação gera problemas imediatos. Os mais comuns:

Estado local. Se a aplicação guarda qualquer coisa em memória — sessões de usuário, cache, filas temporárias — isso não existe nas outras instâncias. Dois requests consecutivos do mesmo usuário podem chegar em servidores diferentes e o segundo não vai "lembrar" do primeiro. A solução é mover o estado para fora: sessões em Redis, cache distribuído, filas em serviços como RabbitMQ ou SQS.

Uploads e arquivos. Se a instância A processa um upload e salva o arquivo localmente, a instância B não tem acesso a ele. Armazenamento de arquivos precisa ser externo — S3, GCS, ou qualquer sistema de arquivos distribuído.

Jobs agendados. Se cada instância roda seu próprio cron, o mesmo job vai executar N vezes. Ou você centraliza a execução em um serviço dedicado, ou usa bibliotecas que garantem execução distribuída com lock.

Esses não são problemas insolúveis, mas precisam ser resolvidos antes de adicionar instâncias — não depois de começar a ver bugs estranhos em produção.

Balanceamento de carga na prática

O componente que distribui requisições entre as instâncias é o load balancer. O HAProxy é uma das opções mais consolidadas para isso, e uma configuração básica funcional tem essa cara:

global
    log stdout format raw local0
    maxconn 100000

defaults
    mode http
    timeout connect 5s
    timeout client  30s
    timeout server  30s
    option redispatch
    retries 3

frontend http_in
    bind *:80
    default_backend app_nodes

backend app_nodes
    balance leastconn
    option httpchk GET /health HTTP/1.1\r\nHost:\ localhost
    http-check expect status 200

    server node1 192.168.1.10:8080 check inter 3s rise 2 fall 3
    server node2 192.168.1.11:8080 check inter 3s rise 2 fall 3
    server node3 192.168.1.12:8080 check inter 3s rise 2 fall 3

O algoritmo leastconn (menor número de conexões ativas) costuma se sair melhor que roundrobin em aplicações web reais, onde os requests têm durações desiguais. O roundrobin simples assume que tudo demora o mesmo tempo — o que raramente é verdade.

Os parâmetros de health check merecem atenção: rise 2 fall 3 significa que o servidor precisa falhar 3 vezes consecutivas para sair do pool, e passar 2 vezes consecutivas para voltar. Isso evita que instâncias intermitentes entrem e saiam do pool a cada check, causando oscilação na distribuição de carga.

Autoescalamento: reagindo à demanda

Gerenciar instâncias manualmente não escala. Em ambientes de nuvem, o padrão é configurar autoescalamento — o sistema adiciona e remove instâncias automaticamente com base em métricas.

No Kubernetes, o Horizontal Pod Autoscaler faz isso com base em CPU, memória, ou métricas customizadas expostas via Prometheus:

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: app-hpa
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: minha-aplicacao
  minReplicas: 2
  maxReplicas: 20
  metrics:
    - type: Resource
      resource:
        name: cpu
        target:
          type: Utilization
          averageUtilization: 70
    - type: Resource
      resource:
        name: memory
        target:
          type: Utilization
          averageUtilization: 80

Com essa configuração, o Kubernetes mantém entre 2 e 20 réplicas, ajustando conforme CPU e memória médias dos pods. O minReplicas: 2 garante que nunca haverá um único ponto de falha, mesmo com tráfego zero.

Em AWS, o equivalente é o Auto Scaling Groups com políticas de scaling baseadas em métricas do CloudWatch.

Banco de dados: o gargalo que aparece depois

Escalar as instâncias de aplicação resolve a camada de computação, mas frequentemente expõe o banco de dados como próximo gargalo. Um único banco relacional respondendo a 20 instâncias de aplicação vai sentir a diferença.

As estratégias aqui dependem do padrão de acesso. Para workloads com muito mais leitura do que escrita, réplicas de leitura distribuem a carga de queries sem comprometer a consistência das escritas. Para workloads de escrita intensiva, sharding ou bancos distribuídos como CockroachDB ou PlanetScale entram em cena.

O erro clássico é escalar a aplicação sem pensar no banco — e descobrir que o problema só mudou de lugar.

O que monitorar

Escala horizontal sem observabilidade é voar às cegas. As métricas que mais importam:

  • Latência por percentil (p50, p95, p99) — médias escondem problemas; os percentis altos revelam o que os usuários mais lentos estão experimentando
  • Taxa de erros por instância — se uma instância específica está gerando mais erros que as outras, ela pode estar com problema de configuração ou hardware
  • Utilização de CPU e memória por pod/instância — base para políticas de autoescalamento
  • Conexões ativas no load balancer — sinaliza distribuição desigual de carga

Ferramentas como Prometheus + Grafana ou soluções gerenciadas como Datadog e New Relic resolvem isso. O ponto crítico é ter dashboards configurados antes de um incidente, não durante.

Escala horizontal bem feita fica invisível para o usuário final — a aplicação simplesmente responde, independente de quantas pessoas estão acessando. O caminho até lá envolve preparar a aplicação para ser stateless, instrumentar bem, e automatizar o que puder ser automatizado. A documentação do Kubernetes sobre escalamento e o guia de arquitetura de sistemas distribuídos da AWS são boas referências para continuar a partir daqui.