← Blog

Load balancer, Nginx e o problema de vários servidores

A instância aguenta até não aguentar. Você sobe RAM, sobe CPU, o p95 continua feio no horário de almoço. Vertical tem teto. O próximo movimento é óbvio no slide e traiçoeiro no login: colocar um Nginx na frente e duas (depois N) cópias da aplicação.

Balancear carga é distribuir request entre processos ou máquinas para que nenhuma peça sozinha vire o gargalo. O Nginx faz isso bem, barato, com pouca mágica. O que quebra não é o upstream. É o estado que você esqueceu debaixo do tapete.

Por que uma caixa maior para

Uma máquina só concentra CPU, disco, rede e falha. Mesmo com recurso de sobra, o teto de conexões, o teto de workers do PHP-FPM e o teto de um único ponto de falha aparecem juntos. Incidente de kernel, de disco, de zona: tudo some junto.

Há também o perfil da carga. Muita request curta convive mal com poucas requests longas na mesma caixa. Relatório de seis segundos ocupa o worker que deveria servir o checkout. Horizontal permite, depois, separar papéis. Primeiro, já permite somar capacidade em fatias.

O teste de carga sincero mostra o platô. Requests por segundo param de crescer, latência sobe, CPU talvez nem esteja em 100%. Você não precisa de um servidor maior. Precisa de mais servidores e de um jeito de chegar neles.

Nginx como porta, não como aplicação

O padrão simples: o Nginx escuta 80/443, termina TLS, serve estático, e o upstream lista app1, app2, app3 — containers, VMs, sockets. least_conn ou round-robin. Health check ou max_fails. Request nova cai em um nó. Se um nó morre, os outros seguem.

Isso já aumenta a capacidade de trabalho stateless. Endpoint que só lê banco e devolve JSON escala quase linear até o banco reclamar. Endpoint que grava upload em /var/www/uploads da máquina local não escala: o arquivo está no disco que a próxima request talvez não veja.

O balanceador na AWS (ALB/NLB) é o mesmo desenho com menos Nginx para gerenciar. O princípio não muda: a porta não guarda o seu $_SESSION.

flowchart TD
  A[Cliente] --> B[Nginx / LB]
  B --> C[App A]
  B --> D[App B]
  B --> E[App C]
  C --> F{Onde está a sessão?}
  D --> F
  E --> F
  F -->|Disco local| G[Login some]
  F -->|Sticky| H[Nó preso, escala falsa]
  F -->|Token ou store| I[Qualquer nó atende]
  classDef ouro fill:#FBF5E4,stroke:#C9A227,color:#0B1F18,stroke-width:1.4px
  classDef verde fill:#F3FAF7,stroke:#1E7A57,color:#0B1F18,stroke-width:1.4px
  classDef fim fill:#14513B,stroke:#0F3328,color:#FBFAF6,stroke-width:1.4px
  class A,B ouro
  class C,D,E,F verde
  class G,H,I fim

Sticky session: o atalho que finge escala

Sessão em arquivo ou em memória do processo vive no nó que autenticou. A próxima request no outro nó não conhece o usuário. O “fix” clássico é sticky (afinidade): o balanceador manda o mesmo cliente sempre ao mesmo backend, por cookie ou IP.

Sticky reduz o sintoma e cria outro. O nó gordo fica gordo. O nó novo sobe vazio. Deploy que derruba o nó A desloga quem estava colado nele. Usuário atrás de NAT aparece como um IP só e concentra carga. Você comprou vários servidores e opera como se fosse um, com failover pior.

Há casos estreitos em que sticky é ponte: WebSocket mal desenhado, legado que você vai matar no trimestre. Como arquitetura, é dívida. O objetivo do horizontal é qualquer nó servir qualquer request autenticada.

Token ou store: o estado sai da máquina

Dois caminhos sadios.

Sessão em store compartilhado. Redis, Memcached, banco. O PHP (ou o framework) deixa de escrever em arquivo local. Qualquer nó lê a mesma sessão. Você ainda tem sessão server-side: logout central, dados maiores, revogação fácil. Paga-se com mais um componente e com a disciplina de não guardar objeto enorme no Redis.

Token. O cliente guarda um token opaco ou um JWT. O servidor valida assinatura ou consulta uma tabela de tokens. Não há afinidade. Escalar nós não mexe no login. O custo muda de lugar: rotação de chave, expiração, revogação, não colocar dado sensível em JWT que o cliente lê, HTTPS obrigatório.

Em API, token costuma ser o caminho mais simples para N servidores. Em app clássica com formulário e cookie de sessão, store compartilhado costuma ser a menor mudança. Os dois vencem sticky. Os dois exigem que arquivo, cache e upload também saiam do disco local — senão você só resolveu o login.

Estado No nó local Com vários nós
Sessão em arquivo Funciona Login fantasma
Sticky session — Escala irregular, deploy doloroso
Sessão no Redis — Qualquer nó atende
Token (Sanctum, JWT, opaco) — Qualquer nó atende
Upload em /tmp Funciona Arquivo some
Cache em APCu só Rápido Inconsistente entre nós

O que mais precisa sair do servidor

Lista curta e ignorada: arquivos de usuário, jobs em fila locais, cron que “roda em um dos nós” sem eleição, cache de opcode é ok (é por processo), cache de dado de negócio em memória local não é ok se a invalidação precisa ser global.

Log também muda. Três nós, três storage/logs. Sem agregar, o incidente vira SSH em roleta. Não é motivo para não balancear. É motivo para tratar observabilidade como parte do horizontal, não como extra.

Deploy: se você sobe código em um nó e o outro fica na versão antiga, o balanceador mistura contratos. Drain, health check, versão. Horizontal sem disciplina de release é incidente intercalado.

Por onde começar sem teatro

  1. Meça o platô da caixa única.
  2. Coloque o Nginx (ou o LB) na frente de duas cópias iguais.
  3. Quebre o login de propósito: acesse, troque o nó, veja a sessão sumir.
  4. Mova sessão para token ou Redis.
  5. Repita o teste de carga. Só então acrescente o terceiro nó.

O passo 3 é o que o tutorial pula. Sem ele, o time descobre sticky em produção, no horário em que o marketing ligou a campanha. Vários servidores não são “mais do mesmo”. São o mesmo código sem estado local. O Nginx só distribui. O desenho é que decide se a distribuição mente.

No PHP One esse ciclo aparece cedo: a request, o FPM, e o que deixa de caber num processo só.

Leia também: Escalabilidade vertical e horizontal e PHP na web: Nginx, FPM e o ciclo da request.

#Nginx#Load balancer#Escalabilidade#Infraestrutura

Thiago Nogueira

Autor de A Caixa Mágica e das séries Programming One e AI One na Editora Pensar Bem.

Capa de Python One

Python One

Do primeiro print à API REST em produção.