Escalabilidade, elasticidade e disponibilidade na prática: dos fundamentos à arquitetura que aguenta uma Black Friday.
São 00h05 da Black Friday. A campanha funcionou, o tráfego saltou de centenas para dezenas de milhares de acessos em minutos, e o e-commerce que rodava num único servidor saiu do ar. Cada segundo de queda é venda perdida.
Esse problema tem solução conhecida. Não é mágica nem exclusividade de gigante: é um conjunto de técnicas que dá pra aplicar de forma incremental. Este artigo mostra cada camada, de "um servidor que cai" até "uma frota que cresce, encolhe e se recupera sozinha".
Três conceitos que não são sinônimos
Antes de tudo, três palavras que costumam ser confundidas.
Escalabilidade é a capacidade de lidar com mais carga adicionando recursos. Isso não implica automático, apenas que o sistema consegue crescer. Há dois caminhos. O vertical (scale up) troca a máquina por uma maior; é simples, mas tem teto físico e costuma exigir downtime. O horizontal (scale out) coloca várias máquinas em paralelo; não tem teto teórico e tolera falhas melhor, mas exige uma aplicação projetada pra isso. É o caminho deste artigo.
Elasticidade é escalabilidade automática e bidirecional. Você pode ser escalável sem ser elástico: adicionar servidores na mão é escalável, não elástico. Elasticidade é crescer sozinho no pico e encolher sozinho na baixa. O encolher importa pro custo: é o que evita pagar pela capacidade de Black Friday e o ano inteiro.
Disponibilidade é a fração de tempo em que o sistema está de pé. Mede-se em "noves": 99,9% são quase 9 horas de queda por ano; 99,99%, cerca de 52 minutos. É independente das outras duas: dá pra ter um sistema elástico que mesmo assim cai, se todas as máquinas estiverem no mesmo lugar e esse lugar falhar.
O resto do artigo preenche esses três espaços.
Comece pelo stateless
Essa é a base, e é a parte que mais tutorial pula.
Se a ideia é ter várias máquinas idênticas atendendo o mesmo site, surge um problema: o que acontece com os dados que ficam dentro de uma máquina, como sessão, carrinho e arquivo enviado? Se cada instância guarda isso localmente, o usuário que cair em outra máquina perde o estado. E quando a máquina morrer (numa frota elástica isso acontece o tempo todo), o dado morre junto.
A regra é tornar a aplicação stateless: a instância não guarda nada importante e único dentro de si. Ela é uma peça descartável, que pode nascer e morrer sem ninguém notar. Todo estado que precisa sobreviver vai pra um serviço dedicado:
- Sessão e cache vão para um cache em memória (Redis/ElastiCache).
- Dados estruturados vão para o banco gerenciado (RDS, DynamoDB).
- Arquivos compartilhados vão para um sistema de arquivos em rede (EFS), montado por várias instâncias ao mesmo tempo.
- Objetos (imagens, uploads, backups) vão para o armazenamento de objetos (S3).
Na AWS, o disco local da instância (EBS) é privado e descartável: pertence a uma máquina só e some quando ela é destruída. Por isso nada que precise sobreviver pode morar nele. E atenção: o EBS não se compartilha entre instâncias como um HD comum. Ele entrega blocos crus, sem coordenar escrita concorrente, e duas máquinas escrevendo nele ao mesmo tempo corrompem o dado. Pra compartilhar de verdade, use EFS (arquivos) ou S3 (objetos).
Sem stateless, escalar horizontalmente não funciona. É a fundação de tudo que vem a seguir.
Coloque um load balancer na frente
Com a aplicação stateless, podemos ter várias instâncias. Mas o cliente digita um endereço só, e a requisição precisa ir pra alguma máquina. Qual?
O load balancer responde isso. Ele é um intermediário entre os clientes e os servidores reais: os clientes conhecem só o endereço dele, e ele decide para qual servidor encaminhar cada requisição. É como um funcionário na entrada das filas do supermercado dizendo "vá ao caixa 3, que está livre". Resolve três coisas de uma vez: capacidade (vários caixas atendem mais), disponibilidade (se um cai, os outros seguem) e distribuição equilibrada.
A decisão segue um algoritmo. Os principais:
- Round robin: alterna em ordem (1, 2, 3, 1, 2, 3). Justo, mas cego: se um servidor engasga, continua mandando tráfego pra ele.
- Least connections: manda a requisição pra quem tem menos conexões abertas. Auto-corretivo: um servidor lento acumula conexões e o algoritmo desvia o tráfego novo dele sozinho. É o melhor pra picos imprevisíveis.
- Weighted: dá pesos diferentes quando os servidores têm capacidades diferentes; os mais fortes recebem mais carga.
O load balancer também precisa saber quem está vivo. Isso é o health check: ele sonda cada servidor periodicamente (em geral um GET /health esperando 200) e tira de rotação quem falhar. Dois cuidados:
- Use limiares consecutivos. Não derrube o servidor na primeira falha; exija, por exemplo, três falhas seguidas. Isso evita o flapping, o servidor piscando por causa de um soluço de rede.
- Não faça o health check fundo demais. Se o
/healthverifica o banco e todas as instâncias compartilham o mesmo banco, um soluço do banco derruba todos os checks ao mesmo tempo, e o balanceador tira a frota inteira de rotação. Um check mais raso mantém os servidores no ar.
Adicione auto scaling
Agora temos um load balancer distribuindo entre, digamos, cinco instâncias. Ainda há um teto.
Na Black Friday, o tráfego passa do que as cinco aguentam juntas. E nenhum algoritmo de balanceamento resolve isso: o least connections só espalha a sobrecarga, deixando as cinco lentas por igual. O load balancer extrai o máximo dos servidores que existem, mas não cria um sexto.
Quem cria é o auto scaling. Você define regras ("se a CPU média passar de 70%, suba uma instância; se cair de 30%, remova uma") e ele cria e destrói máquinas conforme a demanda. É ele que entrega a elasticidade.
Dois pontos importantes:
- Na AWS, o auto scaling é gratuito. Você paga só pelos recursos que ele sobe (instâncias e discos). Ele é orquestração, não está no caminho do tráfego.
- Ele também substitui instâncias mortas. O load balancer detecta a máquina doente e desvia o tráfego, mas não cria outra. Quem repõe capacidade, por carga ou por falha, é o auto scaling.
Como os dois trabalham juntos
Aqui mora o ponto mais mal-entendido. A intuição comum é que o load balancer "avisa" o auto scaling que está sobrecarregado. Não é assim. Os dois não se falam diretamente. O que os conecta é um sistema central de métricas (na AWS, o CloudWatch).
O fluxo é um laço de realimentação:
- Cada componente publica métricas num lugar central. As instâncias publicam CPU e memória; o load balancer publica latência, requisições por segundo, conexões. Ninguém manda recado a ninguém: todos reportam ao painel.
- O auto scaling lê as métricas (não o load balancer) e age quando um limiar é cruzado de forma sustentada.
Na Black Friday, o laço gira assim: o tráfego dispara, o load balancer espalha pelas cinco instâncias, mas elas saturam, com CPU no teto e latência alta. Isso vira número no painel. O auto scaling vê o limiar estourar e cria as instâncias 6 e 7. Elas sobem, passam no health check e entram no pool do load balancer. Como têm zero conexões abertas, o least connections manda o tráfego novo pra elas naturalmente. A carga reequilibra. Quando o pico passa e as métricas caem, o auto scaling encolhe a frota de volta, parando de mandar conexões novas pra instância que vai sair e esperando as atuais terminarem antes de destruí-la.
A chave é que cada um age numa escala de tempo diferente. O load balancer reage em milissegundos, requisição por requisição, mas é cego pra capacidade. O auto scaling reage em minutos: avaliar a métrica, bootar a máquina, respeitar o cooldown. Um cobre o instante, o outro a tendência. Por isso andam juntos.
E a disponibilidade entra aqui: espalhe as instâncias por pelo menos duas ou três Availability Zones (data centers isolados na mesma região). Se uma AZ cair, as outras seguem; o auto scaling repõe capacidade nas zonas saudáveis e o load balancer só manda tráfego pra quem está de pé.
ALB ou NLB?
A escolha do tipo de load balancer nasce de uma decisão: abrir ou não o pacote.
- NLB (camada 4): olha só IP e porta e encaminha sem abrir o pacote. É muito rápido, fala qualquer protocolo TCP/UDP, tem IP estático e deixa o back-end ver o IP real do cliente.
- ALB (camada 7): abre o pacote e lê o conteúdo HTTP. Roteia por caminho ou host (
/apinum grupo,/imgnoutro), integra com WAF e faz terminação de SSL. É um pouco mais lento porque lê cada requisição.
A regra: se a aplicação fala HTTP e você quer rotear por conteúdo, use ALB (o caso da maioria). Se precisa de velocidade extrema, IP fixo ou protocolo não-HTTP, use NLB.
Sobre custo: o auto scaling é grátis, mas o load balancer cobra mesmo parado, com uma taxa fixa por hora só por existir mais um custo variável por tráfego. Não dá pra pausá-lo; ou está rodando e cobrando, ou está deletado. Em ambiente de teste, desligue as instâncias e delete o balanceador se não for usar. Tráfego entre AZs também tem custo próprio.
O objetivo de tudo isso é um só: fazer um conjunto de servidores se comportar como um sistema único que cresce, encolhe e se recupera sozinho, sem o cliente perceber nada. A diferença entre cair e aguentar a Black Friday não é um servidor maior, e sim uma arquitetura que nunca depende de uma máquina só.